導讀
本文系統梳理 Java 並發編程核心知識體系:Java 內存模型 (JMM)、線程實現與調度、同步機制、鎖優化、線程安全類別、常用 API、死鎖與最佳實踐。適合作為面試複習、架構設計參考、疑難排查速查手冊。
一、Java 內存模型 (JMM) —— 並發語義基石
1.1 核心抽象
- 主內存:所有共享變量 (堆中實例字段、靜態字段、數組元素) 存儲地。
- 工作內存:每條線程私有,保存共享變量副本,線程只能操作工作內存,不可直接讀寫主內存。
- 線程間通信:必須經由主內存中轉 (工作內存 → 主內存 → 工作內存)。
1.2 八大原子操作
| 操作 | 作用對象 | 說明 |
| lock / unlock | 主內存 | 鎖定/解鎖變量,互斥訪問 |
| read / load | 主內存 → 工作內存 | 讀取變量值並載入副本 |
| use / assign | 工作內存 | 執行引擎使用值 / 賦值給副本 |
| store / write | 工作內存 → 主內存 | 存儲副本值並寫入主內存 |
規則:read/load、store/write 必須成對出現;不允許無 assign 的 store;新變量只能在主內存誕生;同一時刻僅一線程可 lock;lock 會清空工作內存副本;unlock 前必須同步回主內存。
1.3 volatile 語義
- 可見性:寫入 volatile 變量立即刷新到主內存,讀取時強制從主內存載入。
- 有序性:禁止指令重排優化 (內存屏障);不保證原子性 (複合操作如
i++ 仍需同步)。
- 適用場景:狀態標誌、單次寫入多次讀取、DCL 單例 (JDK 5+)。
1.4 三大特性總結
| 特性 | 實現手段 | 關鍵點 |
| 原子性 | 基本類型讀寫、synchronized、Lock、原子類 | 不可分割的最小操作單元 |
| 可見性 | volatile、synchronized (unlock→lock)、final | 一線程修改,他線程可見 |
| 有序性 | volatile、synchronized (鎖串行)、happens-before | 程序順序 vs 執行順序 |
1.5 Happens-Before 八條規則
- 程序順序規則:線程內按代碼順序。
- 管程鎖定規則:unlock → 後續 lock。
- volatile 變量規則:寫 → 後續讀。
- 線程啟動規則:
start() → 線程內任意操作。
- 線程終止規則:線程內操作 →
join()/isAlive() 返回。
- 線程中斷規則:
interrupt() → 中斷檢測。
- 對象終結規則:構造器完成 →
finalize()。
- 傳遞性:A hb B, B hb C ⇒ A hb C。
本質:前一操作的結果對後一操作可見。與物理時間順序無關。
二、線程實現與調度
2.1 實現模型演進
| 模型 | 內核線程 (KLT) | 用戶線程 (ULT) | 混合 (KLT+ULT) |
| 映射關係 | 1:1 | N:1 | M:N |
| 調度者 | OS 內核 | 用戶態庫 | 雙層調度 |
| 優點 | 真並行、阻塞不影響別線程 | 切換極快、可大規模 | 兼具並行與輕量 |
| 缺點 | 創建/切換開銷大、受限於 OS 線程數 | 阻塞調用掛起整個進程、無法利用多核 | 實現複雜 |
| HotSpot 採用 | ✅ Windows/Linux 1:1 | ❌ | ❌ |
2.2 調度策略
- 搶佔式:OS/JVM 分配時間片,線程不可控制執行時長 (Java 採用)。
- 優先級:1~10 (默認 5),僅作提示,平台映射可能導致優先級倒置/合併,不應依賴優先級控制業務邏輯。
2.3 線程六大狀態
NEW → RUNNABLE ↔ BLOCKED (等待鎖)
↓
WAITING (無超時等待: wait/join/park)
↓
TIMED_WAITING (有超時等待: sleep/wait(n)/join(n)/parkNanos)
↓
TERMINATED
三、線程安全分級與實現策略
3.1 五級分類
- 不可變:
String、Long、enum、final 基本類型/引用。天生線程安全。
- 絕對線程安全:
CopyOnWriteArrayList、ConcurrentHashMap、AtomicInteger。單獨操作安全,復合操作需額外同步。
- 相對線程安全:
Vector、Hashtable、Collections.synchronizedXxx。單方法安全,疊加調用需外部同步。
- 線程兼容:
ArrayList、HashMap、StringBuilder。需調用方同步。
- 線程對立:
System.setIn/Out/Err、Thread.stop/suspend/resume (已棄用)。多線程下必然出錯。
3.2 同步機制對比
| 機制 | 原理 | 優點 | 缺點 | 適用場景 |
| synchronized | 監視器鎖 (Monitor) | JVM 原生、語法簡單、JIT 優化豐富 | 功能單一、無法中斷等待、無公平性選項 | 簡單同步、方法/塊級 |
| ReentrantLock | AQS + CAS | 可中斷、公平/非公平、多 Condition、可查狀態 | 手動 unlock、代碼冗長 | 複雜同步、需高級特性 |
| volatile | 內存屏障 | 輕量、無鎖 | 僅保證可見/有序、不保證原子 | 狀態標誌、單寫多讀 |
| 原子類 (CAS) | CPU 指令級 CAS | 無鎖、高並發計數器 | ABA 問題、長期自旋消耗 CPU | 計數器、序列號、引用更新 |
| ThreadLocal | 線程私有副本 | 無共享、無同步開銷 | 內存洩漏風險 (弱引用 Key)、不解決共享問題 | 請求上下文、數據庫連接、事務管理 |
四、鎖優化進階 (HotSpot 實現)
4.1 鎖升級路徑
無鎖 → 偏向鎖 → 輕量級鎖 → 重量級鎖
(單線程) (無競爭) (輕度競爭) (激烈競爭)
4.2 四種鎖狀態對比
| 鎖狀態 | Mark Word 關鍵信息 | 適用場景 | 升級條件 |
| 無鎖 | HashCode + Age + 01 | 初始狀態 | 首次進入同步塊 |
| 偏向鎖 | ThreadID + Epoch + Age + 01 | 無競爭、單線程重複進入 | 有其他線程嘗試獲取 |
| 輕量級鎖 | 指向棧中 Lock Record | 輕度競爭、短關鍵區 | CAS 失敗、自旋超過閾值 |
| 重量級鎖 | 指向 ObjectMonitor | 激烈競爭、長關鍵區 | 最終形態 |
4.3 關鍵優化技術
- 自旋鎖:避免阻塞/喚醒內核切換,
-XX:PreBlockSpin=10 默認 10 次,適應性自旋根據歷史調整。
- 鎖消除:逃逸分析證明無共享競爭 → JIT 刪除同步代碼。
- 鎖粗化:連續零碎同步合併為大塊,減少鎖獲取/釋放次數。
- 偏向鎖關閉:
-XX:-UseBiasedLocking (JDK 15 默認關閉,JDK 18 移除),高競爭場景偏向鎖反而增加開銷。
五、常用並發工具類速查
| 類別 | 核心功能 | 典型場景 |
| CountDownLatch | 倒計時門闩,await 直到計數歸零 | 主線程等待多任務完成、並發壓測起跑線 |
| CyclicBarrier | 循環柵欄,攔截固定數量線程再放行 | 多階段計算同步、並行矩陣乘法 |
| Semaphore | 信號量,控制同時訪問許可證數量 | 資源池限流、數據庫連接池 |
| Exchanger | 雙線程數據交換點 | 生產者-消費者雙緩衝區交換 |
| Phaser | 階段式同步器,支持動態註冊/註銷 | 多階段流水線、ForkJoin 輔助 |
| CompletableFuture | 異步編排:thenApply/thenCompose/allOf/anyOf | 微服務聚合查詢、異步流水線 |
| BlockingQueue | 阻塞隊列:put/take/offer/poll | 生產者-消費者、線程池任務隊列 |
| ConcurrentHashMap | 分段鎖 (JDK 7) / CAS+synchronized (JDK 8+) | 高並發緩存、計數器 |
| LongAdder / LongAccumulator | 分段累加器,極高吞吐計數 | 統計指標、QPS 計數 |
六、死鎖、活鎖、飢餓與排查
6.1 死鎖四必要條件 (Coffman 條件)
- 互斥:資源獨佔。
- 請求與保持:持有資源同時請求新資源。
- 不剝奪:資源只能主動釋放。
- 循環等待:環狀等待鏈。
6.2 代碼級死鎖示例
// 嵌套鎖順序不一致
Thread A: synchronized(lock1) { synchronized(lock2) { ... } }
Thread B: synchronized(lock2) { synchronized(lock1) { ... } }
// 可能形成 A 持有 1 等 2,B 持有 2 等 1
6.3 預防與排查
- 有序加鎖:全局統一獲取鎖順序 (如按
System.identityHashCode 排序)。
- 定時嘗試鎖:
lock.tryLock(timeout) 避免無限等待。
- 避免嵌套鎖:重構為單一鎖或無鎖數據結構。
- 排查工具:
jstack -l pid 產生線程快照,搜索 BLOCKED 和 waiting to lock;Arthas thread -b 自動檢測死鎖鏈。
七、核心 API 速查表
| 方法 | 作用 | 關鍵行為 |
Object.wait() | 釋放鎖進入等待隊列 | 需持有鎖、可被 notify/中斷/超時喚醒、醒來重新競爭鎖 |
Object.notify/notifyAll() | 喚醒等待隊列線程 | 需持有鎖、不釋放鎖、被喚醒線程進入鎖競爭 |
Thread.sleep(ms) | 當前線程休眠 | 不釋放鎖、到期自動進入 RUNNABLE |
Thread.join() | 等待目標線程終止 | 底層調用 wait()、可中斷 |
Thread.yield() | 讓出 CPU 時間片 | 僅進入 RUNNABLE、不保證調度 |
Thread.interrupt() | 設置中斷標誌 | 阻塞方法拋 InterruptedException、標誌自動清除 |
Thread.interrupted() | 檢測並清除中斷標誌 | 靜態方法、作用於當前線程 |
thread.isAlive() | 線程是否存活 | start() 後、終止前返回 true |
thread.setDaemon(true) | 設置守護線程 | 必須在 start() 前調用、僅 JVM 內部/非業務關鍵用 |
八、最佳實踐清單
- 優先使用高級組件:
BlockingQueue、CompletableFuture、ConcurrentHashMap、LongAdder,減少手寫同步。
- 鎖粒度最小化:只同步必要代碼,避免在同步塊中調用 I/O、RPC、睡眠。
- 不可變優先:
final、Collections.unmodifiableXxx、Record (JDK 14+)。
- 線程池統一管理:
ThreadPoolExecutor 七大參數精確控制,拒絕策略覆蓋業務降級邏輯。
- ThreadLocal 必清理:
try { ... } finally { threadLocal.remove(); },防內存洩漏 (線程池復用場景)。
- 異常處理不吞沒:
catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException(e); } 保持中斷語義。
- 壓測驗證並發安全:JMeter、
ConcurrencyTestRunner (JCStress) 驗證線程安全性。
總結
Java 並發編程是「共享可變狀態」與「同步協調機制」的博弈。掌握 JMM happens-before 語義、鎖升級與優化、AQS 框架原理、高級組件組合、死鎖預防與排查 五大支柱,配合 Arthas、JFR、JCStress 等觀測工具,能構建高吞吐、低延遲、穩定可靠的並發系統。記住:簡單則用 synchronized,複雜則用 Lock/Condition,極致則用無鎖/CAS/不可變。
- 上一篇文章: HTML file 控件樣式美化與文件上傳最佳實踐:從原生到定製化完整指南
- 下一篇文章: Servlet 獲取 HTML 表單多值參數:Select 多選、Radio、Checkbox、同名 Input 完整指南
Copyright ©
程式碼之家 All Rights Reserved