最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

面試與實(shí)戰(zhàn)必備:掌握J(rèn)ava并發(fā)編程關(guān)鍵知識(shí)點(diǎn)(線程池/CAS/性能優(yōu)化)

 更新時(shí)間:2026年05月23日 09:23:37   作者:百錦再@新空間創(chuàng)想科技  
本文從工程視角系統(tǒng)梳理Java并發(fā)編程的關(guān)鍵知識(shí)點(diǎn),主要內(nèi)容包括:并發(fā)問題的本質(zhì)、Java內(nèi)存模型、volatile和synchronized、Lock與AQS、線程池、Future與CompletableFuture、CAS與原子類、并發(fā)容器、死鎖與線上排查、并發(fā)性能優(yōu)化思路等一些常用代碼片段


前言

Java 并發(fā)編程是后端開發(fā)中非常核心的一項(xiàng)能力。

很多線上問題表面上看起來(lái)是“偶發(fā)故障”,實(shí)際上根因都和并發(fā)有關(guān),比如:

  • 線程池被打滿
  • 鎖競(jìng)爭(zhēng)嚴(yán)重
  • 某些數(shù)據(jù)偶爾不一致
  • 接口響應(yīng)時(shí)間抖動(dòng)
  • CPU 飆高但吞吐沒有提升
  • 異步任務(wù)堆積
  • 死鎖
  • 可見性問題
  • 并發(fā)容器誤用

很多人學(xué)習(xí)并發(fā)時(shí)停留在 API 層,知道 synchronized、volatile、ThreadPoolExecutor 的寫法,但遇到線上問題時(shí),仍然很難定位和判斷。

這篇文章從工程視角出發(fā),系統(tǒng)梳理 Java 并發(fā)編程的關(guān)鍵知識(shí)點(diǎn),幫助你把并發(fā)能力真正轉(zhuǎn)化成可落地的工程判斷力。

本文主要內(nèi)容包括:

  • 并發(fā)問題的本質(zhì)
  • Java 內(nèi)存模型
  • volatile 和 synchronized
  • Lock 與 AQS
  • 線程池設(shè)計(jì)
  • Future 與 CompletableFuture
  • CAS 與原子類
  • 并發(fā)容器
  • 死鎖與線上排查
  • 并發(fā)性能優(yōu)化思路

一、為什么并發(fā)問題總是在生產(chǎn)環(huán)境暴露

并發(fā)問題有一個(gè)典型特點(diǎn):

本地不容易復(fù)現(xiàn),線上高峰期集中爆發(fā)。

原因很簡(jiǎn)單。

并發(fā) bug 往往和時(shí)序有關(guān),而時(shí)序又受到下面這些因素影響:

  • 線程調(diào)度
  • CPU 競(jìng)爭(zhēng)
  • GC 暫停
  • 網(wǎng)絡(luò)波動(dòng)
  • 數(shù)據(jù)庫(kù)慢查詢
  • 下游服務(wù)阻塞
  • 機(jī)器負(fù)載

在本地開發(fā)環(huán)境下,請(qǐng)求量小、線程數(shù)少、資源競(jìng)爭(zhēng)弱,很多問題根本暴露不出來(lái)。一旦進(jìn)入高并發(fā)、長(zhǎng)時(shí)間運(yùn)行環(huán)境,隱藏問題就會(huì)被放大。

常見表現(xiàn)包括:

  • 計(jì)數(shù)不準(zhǔn)
  • 數(shù)據(jù)重復(fù)處理
  • 某些請(qǐng)求偶發(fā)失敗
  • 響應(yīng)時(shí)間忽高忽低
  • 線程池任務(wù)堆積
  • 線程數(shù)量異常增長(zhǎng)
  • CPU 占用高
  • 服務(wù)看起來(lái)沒掛,但幾乎無(wú)響應(yīng)

這類問題如果沒有并發(fā)基礎(chǔ),很難真正定位。

二、并發(fā)編程到底在解決什么

并發(fā)編程的本質(zhì)不是“讓代碼顯得高級(jí)”,而是為了在有限資源下提高吞吐、提升響應(yīng)效率,并確保結(jié)果正確。

它主要解決三類問題:

1. 性能問題

多個(gè)任務(wù)并行處理,提高資源利用率。

2. 正確性問題

多個(gè)線程同時(shí)讀寫共享數(shù)據(jù)時(shí),如何保證結(jié)果不出錯(cuò)。

3. 可控性問題

線程數(shù)量、任務(wù)隊(duì)列、鎖競(jìng)爭(zhēng)、資源消耗必須有邊界。

很多開發(fā)者只盯著“并發(fā)更快”,但生產(chǎn)系統(tǒng)里更關(guān)鍵的是:

并發(fā)之后還能不能穩(wěn)定、可控、可排查。

三、進(jìn)程、線程、協(xié)程先區(qū)分清楚

1. 進(jìn)程

進(jìn)程是資源分配的基本單位。一個(gè) Java 應(yīng)用啟動(dòng)后,對(duì)應(yīng)一個(gè)獨(dú)立進(jìn)程。

2. 線程

線程是 CPU 調(diào)度的基本單位。一個(gè)進(jìn)程內(nèi)部可以有多個(gè)線程,這些線程共享堆內(nèi)存、方法區(qū)等資源。

3. 協(xié)程

協(xié)程是更輕量級(jí)的執(zhí)行單元,通常在用戶態(tài)調(diào)度。Java 傳統(tǒng)并發(fā)主要以線程為核心,不過現(xiàn)在虛擬線程也在逐步發(fā)展。

對(duì)于大多數(shù) Java 后端開發(fā)者來(lái)說(shuō),目前最需要掌握的仍然是線程模型,因?yàn)椋?/p>

  • 線程池
  • 并發(fā)容器
  • AQS
  • 可見性問題

這些核心知識(shí)都圍繞線程展開。

四、并發(fā) bug 的根源:共享、競(jìng)爭(zhēng)、時(shí)序

并發(fā)代碼為什么難,根本原因就在于這三件事同時(shí)存在:

1. 共享

多個(gè)線程訪問同一份資源。

2. 競(jìng)爭(zhēng)

多個(gè)線程同時(shí)修改共享資源。

3. 時(shí)序不可預(yù)測(cè)

線程何時(shí)運(yùn)行、何時(shí)切換、誰(shuí)先誰(shuí)后,開發(fā)者很難完全控制。

只要代碼中出現(xiàn)“共享變量”,就要立刻問自己三個(gè)問題:

  • 會(huì)不會(huì)被多個(gè)線程訪問
  • 有沒有寫操作
  • 寫操作是否依賴順序

如果答案是“多個(gè)線程訪問,且至少一個(gè)線程會(huì)寫”,那就必須明確線程安全策略。

五、Java 內(nèi)存模型為什么必須理解

并發(fā)問題很多時(shí)候不是代碼邏輯錯(cuò),而是線程看到的變量值不一致。

Java 內(nèi)存模型,也就是 JMM,規(guī)定了:

  • 線程如何與主內(nèi)存交互
  • 線程何時(shí)能看到其他線程寫入的值
  • 哪些操作能保證有序性和可見性

理解 JMM,至少要掌握三個(gè)概念:

1. 原子性

一個(gè)操作是否不可分割。

2. 可見性

一個(gè)線程修改變量后,另一個(gè)線程能否立刻看到。

3. 有序性

程序執(zhí)行順序是否和代碼書寫順序一致。

舉個(gè)例子:

count++;

這不是原子操作,它至少包括三步:

  • 讀取 count
  • count + 1
  • 寫回 count

因此在多線程下會(huì)出現(xiàn)數(shù)據(jù)競(jìng)爭(zhēng)。

六、volatile 的作用與邊界

volatile 的核心能力是:

  • 保證可見性
  • 一定程度上禁止指令重排序

示例:

private volatile boolean running = true;

一個(gè)線程將 running 改成 false,另一個(gè)線程通??梢院芸熳x到最新值。

volatile 適合什么場(chǎng)景

  • 狀態(tài)開關(guān)
  • 配置刷新標(biāo)記
  • 一次寫、多次讀
  • 雙重檢查單例中的實(shí)例引用

volatile 不適合什么場(chǎng)景

  • 計(jì)數(shù)器自增
  • 余額扣減
  • 復(fù)合條件判斷
  • 依賴“讀-改-寫”原子性的邏輯

例如下面代碼依然線程不安全:

private volatile int count = 0;

public void increment() {
    count++;
}

因?yàn)?count++ 不是原子操作。

所以一定要記住一句話:

volatile 保證可見性,不保證復(fù)合操作原子性。

七、synchronized 的本質(zhì)

synchronized 是 Java 最基礎(chǔ)的內(nèi)置鎖。

它能保證:

  • 同一時(shí)刻只有一個(gè)線程進(jìn)入臨界區(qū)
  • 進(jìn)入和退出同步塊時(shí)具備可見性語(yǔ)義
  • 在同步范圍內(nèi)具備一定有序性保障

示例:

public synchronized void add() {
    count++;
}

或者:

synchronized (lock) {
    count++;
}

synchronized 適合什么場(chǎng)景

  • 簡(jiǎn)單互斥訪問
  • 臨界區(qū)較小
  • 對(duì)鎖控制要求不復(fù)雜
  • 代碼可讀性優(yōu)先

很多開發(fā)者印象里覺得 synchronized 性能很差,這個(gè)認(rèn)知已經(jīng)過時(shí)。JDK 對(duì)它做過大量?jī)?yōu)化,很多場(chǎng)景下完全夠用。

八、ReentrantLock 為什么存在

如果你對(duì)鎖有更細(xì)粒度的控制需求,ReentrantLock 就比 synchronized 更適合。

示例:

private final ReentrantLock lock = new ReentrantLock();

public void add() {
    lock.lock();
    try {
        count++;
    } finally {
        lock.unlock();
    }
}

ReentrantLock 的優(yōu)勢(shì)

  • 支持可中斷獲取鎖
  • 支持 tryLock()
  • 支持超時(shí)獲取鎖
  • 支持公平鎖
  • 支持多個(gè) Condition

什么時(shí)候用 ReentrantLock

  • 需要超時(shí)等待鎖
  • 需要中斷等待鎖
  • 需要多個(gè)條件隊(duì)列
  • 需要更細(xì)致的鎖控制

但同時(shí)要注意:

顯式鎖一定要在 finally 中釋放。

九、AQS 到底是什么

AQS,全稱 AbstractQueuedSynchronizer,是 Java 并發(fā)包里非常核心的同步框架。

很多常見同步工具都建立在 AQS 之上,例如:

  • ReentrantLock
  • Semaphore
  • CountDownLatch
  • ReentrantReadWriteLock
  • FutureTask

AQS 的核心思路可以概括為:

  • 用一個(gè) state 表示同步狀態(tài)
  • 用等待隊(duì)列保存競(jìng)爭(zhēng)失敗的線程
  • 線程獲取資源失敗后排隊(duì)等待
  • 資源釋放后喚醒后繼節(jié)點(diǎn)

理解 AQS 的意義不在于每天自己手寫同步器,而在于你能真正理解:

  • 鎖是怎么排隊(duì)的
  • 線程為什么阻塞
  • 為什么有鎖競(jìng)爭(zhēng)
  • 為什么線程會(huì)被喚醒

十、synchronized 和 ReentrantLock 怎么選

簡(jiǎn)單來(lái)說(shuō)可以這樣判斷:

優(yōu)先用 synchronized 的場(chǎng)景

  • 只需要簡(jiǎn)單互斥
  • 臨界區(qū)邏輯不復(fù)雜
  • 不需要嘗試獲取鎖
  • 不需要超時(shí)控制
  • 更看重代碼簡(jiǎn)潔

優(yōu)先用 ReentrantLock 的場(chǎng)景

  • 需要可中斷
  • 需要 tryLock
  • 需要公平鎖
  • 需要多個(gè)條件變量
  • 需要更細(xì)粒度鎖控制

不要為了“顯得專業(yè)”把所有同步都替換成 ReentrantLock。大多數(shù)場(chǎng)景,簡(jiǎn)單的方案更穩(wěn)。

十一、讀寫鎖什么時(shí)候適合使用

如果一個(gè)共享資源是典型的“讀多寫少”,讀寫鎖通常能帶來(lái)更好的并發(fā)能力。

示例:

private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
private final Lock readLock = rwLock.readLock();
private final Lock writeLock = rwLock.writeLock();

讀操作:

readLock.lock();
try {
    return cache.get(key);
} finally {
    readLock.unlock();
}

寫操作:

writeLock.lock();
try {
    cache.put(key, value);
} finally {
    writeLock.unlock();
}

適合讀寫鎖的場(chǎng)景

  • 緩存
  • 配置讀取
  • 字典數(shù)據(jù)
  • 查詢遠(yuǎn)多于更新的共享資源

不適合的場(chǎng)景

  • 寫操作頻繁
  • 臨界區(qū)很小
  • 鎖維護(hù)成本高于收益

十二、線程池為什么是并發(fā)系統(tǒng)的核心

生產(chǎn)環(huán)境里,幾乎不應(yīng)該頻繁手動(dòng) new Thread()。

原因很簡(jiǎn)單:

  • 線程創(chuàng)建和銷毀有成本
  • 線程數(shù)不受控會(huì)壓垮系統(tǒng)
  • 線程切換也有成本
  • 缺乏統(tǒng)一管理和監(jiān)控

線程池的核心價(jià)值就是:

統(tǒng)一管理線程資源,并建立明確邊界。

線程池的關(guān)鍵參數(shù)包括:

  • corePoolSize
  • maximumPoolSize
  • keepAliveTime
  • workQueue
  • threadFactory
  • RejectedExecutionHandler

示例:

ExecutorService executor = new ThreadPoolExecutor(
        8,
        16,
        60L,
        TimeUnit.SECONDS,
        new ArrayBlockingQueue<>(1000),
        Executors.defaultThreadFactory(),
        new ThreadPoolExecutor.CallerRunsPolicy()
);

十三、為什么生產(chǎn)環(huán)境不建議直接用 Executors

很多人習(xí)慣直接這樣寫:

Executors.newFixedThreadPool(10);

或者:

Executors.newCachedThreadPool();

這類 API 雖然方便,但在生產(chǎn)環(huán)境通常不推薦直接使用,原因是關(guān)鍵參數(shù)被隱藏了。

典型風(fēng)險(xiǎn)

1. newFixedThreadPool

默認(rèn)使用無(wú)界隊(duì)列,任務(wù)堆積時(shí)可能不斷占用內(nèi)存。

2. newCachedThreadPool

線程數(shù)增長(zhǎng)過快時(shí)可能導(dǎo)致大量線程創(chuàng)建,造成調(diào)度和資源壓力。

生產(chǎn)環(huán)境更合理的方式是:

顯式使用 ThreadPoolExecutor,把線程數(shù)、隊(duì)列長(zhǎng)度、拒絕策略寫清楚。

十四、線程池參數(shù)如何估算

線程池沒有萬(wàn)能配置,但可以從任務(wù)類型入手。

1. CPU 密集型任務(wù)

例如:

  • 加密解密
  • 規(guī)則計(jì)算
  • 圖像處理
  • 復(fù)雜算法

這類任務(wù)線程數(shù)通常接近 CPU 核數(shù)。

經(jīng)驗(yàn)值:

CPU核數(shù) + 1

2. IO 密集型任務(wù)

例如:

  • 數(shù)據(jù)庫(kù)訪問
  • Redis 調(diào)用
  • HTTP 調(diào)用
  • 文件讀寫

這類任務(wù)線程經(jīng)常在等待,可以適當(dāng)提高線程數(shù)。

3. 真正的調(diào)優(yōu)原則

線程池參數(shù)不能只靠經(jīng)驗(yàn)公式,最終一定要看:

  • 接口響應(yīng)時(shí)間
  • 隊(duì)列堆積情況
  • 拒絕次數(shù)
  • CPU 使用率
  • 下游資源瓶頸

十五、拒絕策略為什么不能忽略

線程池不是無(wú)限吞任務(wù)的。

當(dāng)線程數(shù)達(dá)到上限、隊(duì)列也滿了之后,線程池會(huì)觸發(fā)拒絕策略。

JDK 常見拒絕策略有四種:

  • AbortPolicy
  • CallerRunsPolicy
  • DiscardPolicy
  • DiscardOldestPolicy

各自特點(diǎn)

1. AbortPolicy

直接拋異常。適合需要明確感知失敗的場(chǎng)景。

2. CallerRunsPolicy

由提交任務(wù)的線程自己執(zhí)行,適合做一定程度的反壓。

3. DiscardPolicy

直接丟棄任務(wù),不拋異常。高風(fēng)險(xiǎn),不適合關(guān)鍵任務(wù)。

4. DiscardOldestPolicy

丟棄最早排隊(duì)的任務(wù)。是否合理取決于業(yè)務(wù)語(yǔ)義。

最危險(xiǎn)的不是哪種策略不好,而是:

系統(tǒng)已經(jīng)在拒絕任務(wù)了,但沒人知道。

所以拒絕次數(shù)一定要納入監(jiān)控。

十六、Future、Callable、CompletableFuture 的使用場(chǎng)景

1. Callable 和 Future

Callable 相比 Runnable,支持返回值和異常。

示例:

Future<Integer> future = executor.submit(() -> 1 + 2);
Integer result = future.get();

問題在于:

  • Future 組合能力差
  • 多任務(wù)編排麻煩
  • 異常傳播不優(yōu)雅

2. CompletableFuture

如果需要做異步編排,CompletableFuture 更適合。

例如:

CompletableFuture<User> userFuture =
        CompletableFuture.supplyAsync(() -> userService.getUser(userId), executor);

CompletableFuture<List<Order>> orderFuture =
        CompletableFuture.supplyAsync(() -> orderService.getOrders(userId), executor);

CompletableFuture<UserDetailDTO> resultFuture =
        userFuture.thenCombine(orderFuture, UserDetailDTO::new);

使用 CompletableFuture 的注意點(diǎn)

  • 生產(chǎn)環(huán)境盡量顯式指定線程池
  • 不要把所有業(yè)務(wù)都異步化
  • 要明確異常處理鏈路
  • 不要忽略下游資源瓶頸

十七、CAS 和原子類的意義

CAS 是 Compare-And-Swap,通過比較當(dāng)前值與期望值是否一致來(lái)決定是否更新。

Java 中常見原子類包括:

  • AtomicInteger
  • AtomicLong
  • AtomicReference
  • LongAdder

示例:

AtomicInteger counter = new AtomicInteger(0);
counter.incrementAndGet();

CAS 的優(yōu)點(diǎn)

  • 輕量
  • 低沖突下性能好
  • 無(wú)需傳統(tǒng)互斥鎖

CAS 的限制

  • 高沖突時(shí)會(huì)反復(fù)重試
  • 會(huì)消耗 CPU
  • 不適合復(fù)雜臨界區(qū)邏輯
  • 存在 ABA 問題

所以原子類適合做:

  • 計(jì)數(shù)器
  • 狀態(tài)位切換
  • 簡(jiǎn)單引用更新

不適合直接替代所有加鎖場(chǎng)景。

十八、AtomicLong 和 LongAdder 怎么選

AtomicLong 適合

  • 并發(fā)不高
  • 單點(diǎn)計(jì)數(shù)
  • 需要簡(jiǎn)單直接

LongAdder 適合

  • 高并發(fā)熱點(diǎn)計(jì)數(shù)
  • 指標(biāo)統(tǒng)計(jì)
  • 訪問次數(shù)累加
  • 監(jiān)控場(chǎng)景

原因是 LongAdder 會(huì)分散競(jìng)爭(zhēng)熱點(diǎn),最后再匯總結(jié)果,在高并發(fā)統(tǒng)計(jì)場(chǎng)景下通常更有優(yōu)勢(shì)。

十九、并發(fā)容器如何選型

常見并發(fā)容器包括:

  • ConcurrentHashMap
  • CopyOnWriteArrayList
  • ConcurrentLinkedQueue
  • BlockingQueue

1. ConcurrentHashMap

適合緩存、本地索引、狀態(tài)映射。

ConcurrentHashMap<String, Object> cache = new ConcurrentHashMap<>();

2. CopyOnWriteArrayList

適合讀多寫少場(chǎng)景,比如監(jiān)聽器列表。

3. BlockingQueue

適合生產(chǎn)者消費(fèi)者模型、異步任務(wù)緩沖。

4. ConcurrentLinkedQueue

適合高并發(fā)無(wú)阻塞隊(duì)列場(chǎng)景。

這里一定要記住一句話:

并發(fā)容器保證的是單次容器操作線程安全,不保證多個(gè)組合操作天然線程安全。

例如下面寫法就不是絕對(duì)安全的:

if (!map.containsKey(key)) {
    map.put(key, value);
}

應(yīng)優(yōu)先使用:

map.putIfAbsent(key, value);

或者:

map.computeIfAbsent(key, k -> createValue(k));

二十、阻塞隊(duì)列的重要性

阻塞隊(duì)列在并發(fā)系統(tǒng)里非常關(guān)鍵,尤其在線程池和生產(chǎn)者消費(fèi)者模型中。

常見阻塞隊(duì)列有:

  • ArrayBlockingQueue
  • LinkedBlockingQueue
  • SynchronousQueue
  • DelayQueue
  • PriorityBlockingQueue

使用阻塞隊(duì)列時(shí)要考慮什么

  • 是否需要有界
  • 是否允許任務(wù)堆積
  • 是否需要優(yōu)先級(jí)
  • 是否是延遲任務(wù)
  • 是否需要直接移交

這里最重要的一個(gè)原則是:

高并發(fā)系統(tǒng)優(yōu)先考慮有界隊(duì)列。

無(wú)界隊(duì)列會(huì)把問題“延后爆炸”,而不是解決問題。

二十一、死鎖為什么難排查

死鎖通常發(fā)生在多個(gè)線程相互等待對(duì)方釋放資源時(shí)。

典型場(chǎng)景:

  • 線程 A 持有鎖 A,等待鎖 B
  • 線程 B 持有鎖 B,等待鎖 A

示例:

synchronized (lockA) {
    synchronized (lockB) {
    }
}

另一段代碼反過來(lái):

synchronized (lockB) {
    synchronized (lockA) {
    }
}

預(yù)防死鎖的原則

  • 統(tǒng)一加鎖順序
  • 減少鎖嵌套
  • 縮小鎖粒度
  • 避免持鎖期間做遠(yuǎn)程調(diào)用
  • 必要時(shí)使用 tryLock + timeout

線上排查死鎖最常用的手段是:

jstack

二十二、ThreadLocal 為什么常用又危險(xiǎn)

ThreadLocal 用來(lái)為每個(gè)線程保存獨(dú)立變量副本。

適合場(chǎng)景:

  • 當(dāng)前登錄用戶上下文
  • traceId
  • 請(qǐng)求級(jí)上下文
  • 格式化對(duì)象緩存

示例:

private static final ThreadLocal<String> CURRENT_USER = new ThreadLocal<>();

風(fēng)險(xiǎn)點(diǎn)

如果在線程池環(huán)境中使用 ThreadLocal,但沒有及時(shí)清理,線程復(fù)用后就可能讀到舊值,甚至導(dǎo)致內(nèi)存泄漏。

正確寫法:

try {
    CURRENT_USER.set(userId);
    // 業(yè)務(wù)邏輯
} finally {
    CURRENT_USER.remove();
}

結(jié)論很明確:

線程池環(huán)境中使用 ThreadLocal,一定要 remove。

二十三、并發(fā)性能優(yōu)化中的常見誤區(qū)

誤區(qū) 1:線程越多越快

錯(cuò)誤。線程太多會(huì)帶來(lái)上下文切換和資源競(jìng)爭(zhēng)。

誤區(qū) 2:鎖一定很慢

錯(cuò)誤。低競(jìng)爭(zhēng)場(chǎng)景下鎖可能非常高效。

誤區(qū) 3:異步一定比同步高級(jí)

錯(cuò)誤。異步只是改變執(zhí)行方式,不一定更優(yōu)。

誤區(qū) 4:CPU 高說(shuō)明系統(tǒng)在努力工作

錯(cuò)誤。高 CPU 可能是自旋、死循環(huán)、過度序列化、GC 或競(jìng)爭(zhēng)。

誤區(qū) 5:用了線程池就萬(wàn)事大吉

錯(cuò)誤。線程池參數(shù)配置、隊(duì)列長(zhǎng)度、拒絕策略、任務(wù)拆分方式都會(huì)決定最終效果。

并發(fā)優(yōu)化必須建立在真實(shí)監(jiān)控和壓測(cè)數(shù)據(jù)之上,而不是憑感覺。

二十四、線上并發(fā)問題如何排查

排查并發(fā)問題時(shí),建議按下面順序看。

1. 先看癥狀

  • 是響應(yīng)慢
  • 還是錯(cuò)誤率高
  • 還是吞吐下降
  • 還是 CPU 飆高
  • 還是線程數(shù)過多

2. 看線程池

  • 活動(dòng)線程數(shù)
  • 隊(duì)列長(zhǎng)度
  • 拒絕次數(shù)
  • 最大線程數(shù)是否打滿

3. 看線程棧

使用 jstack 查看:

  • 是否有大量 BLOCKED 線程
  • 是否有 WAITING 線程堆積
  • 是否有死鎖
  • 是否有長(zhǎng)時(shí)間卡在 IO

4. 看下游資源

  • 數(shù)據(jù)庫(kù)連接池是否耗盡
  • Redis 是否超時(shí)
  • HTTP 下游是否變慢

5. 看 GC

長(zhǎng)時(shí)間 STW 也會(huì)放大并發(fā)問題表象。

6. 看業(yè)務(wù)設(shè)計(jì)

  • 是否線程池共用導(dǎo)致互相影響
  • 是否任務(wù)拆分太碎
  • 是否熱點(diǎn) key 導(dǎo)致競(jìng)爭(zhēng)
  • 是否異步任務(wù)沒有邊界

二十五、一個(gè)典型線程池事故案例

假設(shè)一個(gè)訂單系統(tǒng),一個(gè)請(qǐng)求會(huì)觸發(fā)這些動(dòng)作:

  • 查用戶
  • 查庫(kù)存
  • 創(chuàng)建訂單
  • 發(fā)短信
  • 發(fā)通知
  • 寫日志
  • 更新推薦系統(tǒng)

開發(fā)者為了“提升性能”,把后面所有動(dòng)作都異步化,丟進(jìn)同一個(gè)線程池。

線程池參數(shù)如下:

  • 核心線程 20
  • 最大線程 200
  • 無(wú)界隊(duì)列

低峰期沒問題,高峰期庫(kù)存服務(wù)一慢,異步任務(wù)消費(fèi)速度下降。因?yàn)殛?duì)列無(wú)界,任務(wù)會(huì)不斷積壓。短時(shí)間內(nèi)看不出異常,但隨著任務(wù)越堆越多,內(nèi)存、GC、延遲都會(huì)變差,最后系統(tǒng)整體雪崩。

這個(gè)案例說(shuō)明幾個(gè)關(guān)鍵問題:

  • 線程池不能混用所有業(yè)務(wù)
  • 無(wú)界隊(duì)列非常危險(xiǎn)
  • 下游慢必須有超時(shí)和降級(jí)
  • 線程池必須監(jiān)控
  • 異步并不等于高性能

二十六、并發(fā)代碼設(shè)計(jì)的硬規(guī)則

下面這些原則非常實(shí)用。

1. 優(yōu)先減少共享狀態(tài)

不共享,就沒有競(jìng)爭(zhēng)。

2. 顯式創(chuàng)建線程池

不要依賴默認(rèn)線程池或偷懶工廠方法。

3. 所有共享變量都要明確線程安全策略

要么鎖、要么原子類、要么線程封閉、要么不可變。

4. 組合操作要特別謹(jǐn)慎

容器線程安全不代表業(yè)務(wù)邏輯整體線程安全。

5. 鎖粒度盡量小

持鎖期間不要做慢操作、遠(yuǎn)程調(diào)用、數(shù)據(jù)庫(kù)操作。

6. 高并發(fā)熱點(diǎn)計(jì)數(shù)優(yōu)先用 LongAdder

不要讓所有線程爭(zhēng)搶同一個(gè)熱點(diǎn)原子變量。

7. 線程池、鎖、隊(duì)列必須有監(jiān)控

不可觀測(cè)的并發(fā)系統(tǒng)基本不可維護(hù)。

8. 不要為了并發(fā)而并發(fā)

如果真正瓶頸在數(shù)據(jù)庫(kù)或網(wǎng)絡(luò),多開線程不一定有效。

二十七、如何高效學(xué)習(xí) Java 并發(fā)

學(xué)習(xí)并發(fā)最有效的方式不是死記硬背,而是結(jié)合代碼和問題去理解。

建議從三個(gè)方向入手。

1. 自己寫小實(shí)驗(yàn)

例如:

  • 多線程計(jì)數(shù)器
  • volatile 可見性測(cè)試
  • synchronized 和 AtomicInteger 對(duì)比
  • 線程池參數(shù)實(shí)驗(yàn)

2. 結(jié)合源碼學(xué)習(xí)

重點(diǎn)建議看:

  • ThreadPoolExecutor
  • ConcurrentHashMap
  • ReentrantLock
  • CountDownLatch
  • CompletableFuture

3. 帶著線上問題學(xué)習(xí)

比如:

  • 為什么線程池會(huì)堆積
  • 為什么接口偶發(fā)超時(shí)
  • 為什么 CPU 高但吞吐沒上去
  • 為什么會(huì)死鎖

有問題驅(qū)動(dòng),理解會(huì)更扎實(shí)。

總結(jié)

Java 并發(fā)編程不是幾個(gè) API 的使用技巧,而是一整套關(guān)于共享資源、執(zhí)行時(shí)序、資源邊界和系統(tǒng)穩(wěn)定性的工程能力。

如果把全文壓縮成幾條最重要的結(jié)論,就是下面這些:

  • 線程安全的核心不是加鎖,而是控制共享和競(jìng)爭(zhēng)
  • volatile 解決可見性,不解決復(fù)合操作原子性
  • 簡(jiǎn)單互斥優(yōu)先 synchronized,復(fù)雜控制再考慮 Lock
  • 線程池必須顯式配置,參數(shù)和隊(duì)列一定要可控
  • 并發(fā)容器保證單次操作安全,不保證復(fù)合邏輯天然安全
  • 并發(fā)優(yōu)化必須基于監(jiān)控和壓測(cè),不要憑感覺調(diào)整
  • 線上并發(fā)問題本質(zhì)上往往是資源模型和邊界設(shè)計(jì)問題

真正掌握并發(fā),不是會(huì)背定義,而是你開始能在寫業(yè)務(wù)代碼時(shí)主動(dòng)識(shí)別:

  • 哪些狀態(tài)會(huì)共享
  • 哪些地方會(huì)競(jìng)爭(zhēng)
  • 哪些操作需要隔離
  • 哪些任務(wù)需要限流
  • 哪些線程池必須拆分
  • 哪些鎖會(huì)成為瓶頸

當(dāng)你具備這種判斷力時(shí),并發(fā)才真正成為你的工程能力,而不是面試題。

常用代碼片段匯總

synchronized

public synchronized void add() {
    count++;
}

ReentrantLock

lock.lock();
try {
    count++;
} finally {
    lock.unlock();
}

AtomicInteger

AtomicInteger counter = new AtomicInteger(0);
counter.incrementAndGet();

ThreadPoolExecutor

ExecutorService executor = new ThreadPoolExecutor(
        8, 16, 60L, TimeUnit.SECONDS,
        new ArrayBlockingQueue<>(1000),
        Executors.defaultThreadFactory(),
        new ThreadPoolExecutor.CallerRunsPolicy()
);

CompletableFuture

CompletableFuture<User> userFuture =
        CompletableFuture.supplyAsync(() -> userService.getUser(userId), executor);

到此這篇關(guān)于面試與實(shí)戰(zhàn)必備:掌握J(rèn)ava并發(fā)編程關(guān)鍵知識(shí)點(diǎn)(線程池/CAS/性能優(yōu)化)的文章就介紹到這了,更多相關(guān)Java并發(fā)編程核心知識(shí)點(diǎn)梳理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java實(shí)現(xiàn)添加、驗(yàn)證PDF數(shù)字簽名的方法示例

    Java實(shí)現(xiàn)添加、驗(yàn)證PDF數(shù)字簽名的方法示例

    在設(shè)置文檔內(nèi)容保護(hù)的方法中,除了對(duì)文檔加密、添加水印外,應(yīng)用數(shù)字簽名也是一種有效防偽手段。本文就使用Java實(shí)現(xiàn)添加、驗(yàn)證PDF數(shù)字簽名,感興趣的可以了解一下
    2021-07-07
  • Java填充替換數(shù)組元素實(shí)例詳解

    Java填充替換數(shù)組元素實(shí)例詳解

    這篇文章主要通過兩個(gè)實(shí)例說(shuō)明Java填充和替換數(shù)組中元素的方法,需要的朋友可以參考下。
    2017-08-08
  • Java中的runnable 和 callable 區(qū)別解析

    Java中的runnable 和 callable 區(qū)別解析

    Runnable接口用于定義不需要返回結(jié)果的任務(wù),而Callable接口可以返回結(jié)果并拋出異常,通常與Future結(jié)合使用,Runnable適用于簡(jiǎn)單的后臺(tái)任務(wù)和定時(shí)任務(wù),而Callable適用于并行計(jì)算、異步操作和復(fù)雜任務(wù),選擇使用哪個(gè)接口取決于具體的應(yīng)用場(chǎng)景,感興趣的朋友一起看看吧
    2025-03-03
  • java中xml進(jìn)行報(bào)文發(fā)送和解析操作

    java中xml進(jìn)行報(bào)文發(fā)送和解析操作

    這篇文章主要介紹了java中xml進(jìn)行報(bào)文發(fā)送和解析操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來(lái)看看吧
    2020-10-10
  • jdk15的安裝與配置全過程記錄

    jdk15的安裝與配置全過程記錄

    這篇文章主要給大家介紹了關(guān)于jdk15的安裝與配置,文中通過圖文介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-01-01
  • Java得到一個(gè)整數(shù)的絕對(duì)值,不使用任何判斷和比較語(yǔ)句,包括API

    Java得到一個(gè)整數(shù)的絕對(duì)值,不使用任何判斷和比較語(yǔ)句,包括API

    Java得到一個(gè)整數(shù)的絕對(duì)值,不使用任何判斷和比較語(yǔ)句,包括API
    2009-09-09
  • SpringAOP 設(shè)置注入的實(shí)現(xiàn)步驟

    SpringAOP 設(shè)置注入的實(shí)現(xiàn)步驟

    這篇文章主要介紹了SpringAOP 設(shè)置注入的實(shí)現(xiàn)步驟,幫助大家更好的理解和學(xué)習(xí)使用Spring框架,感興趣的朋友可以了解下
    2021-05-05
  • SpringBoot中的自定義starter

    SpringBoot中的自定義starter

    這篇文章主要介紹了SpringBoot中的自定義starter,Starter是Spring?Boot中的一個(gè)非常重要的概念,Starter相當(dāng)于模塊,它能將模塊所需的依賴整合起來(lái)并對(duì)模塊內(nèi)的Bean根據(jù)環(huán)境(條件)進(jìn)行自動(dòng)配置,需要的朋友可以參考下
    2024-01-01
  • SpringBoot 集成 Memcached的方法示例

    SpringBoot 集成 Memcached的方法示例

    這篇文章主要介紹了SpringBoot 集成 Memcached的方法示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-05-05
  • JDBC數(shù)據(jù)源連接池配置及應(yīng)用

    JDBC數(shù)據(jù)源連接池配置及應(yīng)用

    這篇文章主要介紹JDBC建立數(shù)據(jù)庫(kù)連接的兩種方式,使用配置數(shù)據(jù)源的方式連接數(shù)據(jù)庫(kù),效率更高,推薦使用,希望能給大家做一個(gè)參考。
    2016-06-06

最新評(píng)論

荥经县| 南城县| 安达市| 广南县| 武强县| 房山区| 炉霍县| 长沙县| 滦平县| 庄浪县| 会同县| 博白县| 聂拉木县| 曲水县| 南部县| 建宁县| 安泽县| 安顺市| 六枝特区| 金溪县| 衡阳县| 环江| 黔西县| 清涧县| 鄂尔多斯市| 汝城县| 龙里县| 栾川县| 瓮安县| 合阳县| 靖宇县| 遂昌县| 花垣县| 缙云县| 吉林省| 竹溪县| 延吉市| 临颍县| 洛浦县| 东海县| 宁海县|