一、為什么需要再談“鎖”
并發是 Java 的靈魂,而鎖是并發的節拍器。
當多線程同時讀寫共享變量,當微服務跨節點競爭資源,當 CPU 核心數突破三位數,鎖的形態、粒度、語義都在飛速演化。
本文嘗試用近四千字,把 Java 生態中的各類鎖——從語言級到 JVM 級,再到框架級與分布式級——串成一條思維鏈,幫助你下一次排查“線程饑餓”或“死鎖”時,能一眼定位癥結所在。
二、鎖的初心:臨界區與可見性
并發編程的底層矛盾是“原子性”與“可見性”。
臨界區是“必須一次性完成的動作”;可見性是“一個線程的修改何時被另一個線程感知”。
鎖用互斥保證原子,用內存屏障保證可見,兩者缺一不可。
理解這一點,再復雜的鎖都只是“臨界區+可見性”的不同實現。
三、語言級鎖:synchronized 的三次進化
1. 早期重量級
早期 synchronized 直接映射為 OS 互斥量,線程阻塞與喚醒需要內核介入,性能猶如“高速公路上的收費站”。
2. 偏向鎖
JVM 檢測到只有一個線程反復進入臨界區,把鎖標記“偏向”該線程,后續進入無需 CAS,減少無競爭場景開銷。
3. 輕量級鎖
當第二個線程出現時,偏向鎖升級為輕量級鎖,通過 CAS 操作在對象頭里“搶鎖”,失敗后再膨脹為重量級鎖。
4. 自旋與適應性自旋
短暫阻塞時讓線程忙等若干循環,避免上下文切換;JVM 會根據歷史成功率動態調整自旋次數。
一句話:synchronized 現在是一條“可伸縮的鎖鏈”,從無競爭到高競爭,自動選擇最輕的形態。
四、顯式鎖:Lock 接口的萬花筒
- ReentrantLock:可重入、可中斷、可限時、可公平/非公平。
- ReadWriteLock:讀共享、寫獨占,在讀多寫少場景提升并發度。
- StampedLock:樂觀讀、悲觀讀、寫鎖三種模式,通過版本戳避免寫饑餓。
- Condition:精準喚醒,實現“生產者-消費者”的精細化協作。
顯式鎖把“鎖策略”交回開發者手中,代價是需要手動釋放,稍有疏忽便可能“忘記解鎖”。
五、原子變量:鎖的極致輕量
AtomicInteger、AtomicLong、AtomicReference 利用 CPU 的 CAS 指令,把鎖粒度壓縮到單個變量。
ABA 問題由版本戳(AtomicStampedReference)解決;
高并發下的 CAS 失敗重試可能導致 CPU 空轉,于是有了 LongAdder 的“分段累加”思想。
原子變量證明了:當臨界區足夠小時,鎖可以“消失”。
六、JVM 級鎖優化:從對象頭到內存屏障
1. 對象頭 Mark Word
記錄鎖狀態、哈希碼、GC 分代年齡,一條 64 位指令完成狀態切換。
2. 內存屏障
LoadLoad、StoreStore、LoadStore、StoreLoad 四種屏障確保指令重排不會破壞可見性。
3. 逃逸分析
若對象只在單線程內使用,JVM 會“消除鎖”,把同步塊優化成普通代碼。
4. 鎖粗化與鎖消除
連續加鎖解鎖會被合并;不可能共享的對象會被去掉鎖。
七、并發容器:鎖的“隱身術”
ConcurrentHashMap 在 Java 8 之前用分段鎖,之后改為 CAS + synchronized 鏈表/紅黑樹頭節點,鎖粒度降到單個桶。
CopyOnWriteArrayList 用寫時復制,讀操作完全無鎖,適合讀多寫少的廣播場景。
BlockingQueue 家族用“雙鎖+條件變量”實現生產-消費解耦,避免全局鎖。
八、框架級鎖:讀寫世界的再抽象
- StampedLock 的三態模型:樂觀讀 → 悲觀讀 → 寫鎖,通過 stamp 版本號避免寫饑餓。
- Guava Striped 鎖:把對象哈希到 N 條鎖帶,降低鎖競爭。
- Netty 輕量級鎖:利用 FastThreadLocal 減少同步開銷。
這些框架把 JDK 原語包裝成更易用的 DSL,但仍需理解底層語義。
九、分布式鎖:跨進程的閘門
1. 基于數據庫
用唯一索引或行級鎖實現,簡單卻存在單點故障。
2. 基于緩存
通過 SET NX + 過期時間實現,支持高并發,但需處理“鎖續期”與“時鐘漂移”。
3. 基于共識
利用分布式一致性算法(Raft、ZAB)選舉鎖持有者,保證強一致,但吞吐受限。
4. RedLock 爭議
多節點多數派加鎖,臨界場景仍需權衡一致性與可用性。
分布式鎖的核心矛盾:網絡延遲與單點故障之間的永恒拉鋸。
十、鎖的可見性問題:volatile 與 happens-before
鎖不僅排他,還建立 happens-before 關系:
- 解鎖前的寫操作對后續加鎖線程可見。
- volatile 變量寫之后的讀操作保證可見性,但不保證原子性。
理解“鎖-內存屏障-volatile”三角關系,才能寫出正確并發程序。
十一、死鎖、活鎖、饑餓:鎖的副作用
- 死鎖:互相等待對方釋放鎖,形成循環。
預防法:一次性申請全部資源;或按全局順序加鎖。
- 活鎖:線程不斷重試但始終失敗。
解決法:退避策略 + 隨機等待。
- 饑餓:某些線程長期拿不到鎖。
解決法:公平鎖或隊列鎖。
線上排查三板斧:線程 dump、鎖分析工具、業務日志。
十二、性能調優:鎖爭用與 CPU 親和
- 鎖爭用檢測:查看阻塞線程棧、鎖持有時間、競爭次數。
- 鎖分離:把一把大鎖拆成多把小鎖。
- CPU 親和:讓線程固定在某個核心,減少緩存失效。
- 讀寫分離:讀操作無鎖,寫操作串行。
- 自旋時間:根據 CPU 核心數與業務延遲動態調整。
十三、未來趨勢:鎖的消亡與演進
- 無鎖算法:CAS、原子累加、Disruptor 環形緩沖區,把同步開銷降到 CPU 指令級。
- 協程與虛擬線程:把阻塞代價從內核態降到用戶態。
- 硬件事務內存(HTM):CPU 原生支持“事務性”內存操作,失敗即回滾。
- 分布式共識升級:Raft 與多副本緩存結合,兼顧性能與一致。
十四、結語:鎖的宇宙觀
從最輕的原子變量到最重的分布式鎖,
從單核時代的 synchronized 到多核時代的無鎖算法,
鎖的形態不斷演化,核心矛盾卻始終圍繞“互斥、可見、性能”三重張力。
理解鎖,不僅是掌握 API,更是理解并發世界的底層節拍。
當你在深夜排查“線程卡死”時,請記住:
鎖不是敵人,而是并發秩序的第一道閘門。