實現(xiàn)原理)
1. 從AQS到ReentrantLock的實現(xiàn)脈絡第一次拆解ReentrantLock源碼時我被AbstractQueuedSynchronizerAQS這個看似復雜的框架震驚了。這個隱藏在java.util.concurrent包中的同步器實際上構建了Java并發(fā)編程的半壁江山。理解AQS的工作機制就像拿到了打開JUC包所有鎖實現(xiàn)的金鑰匙。AQS的核心是一個volatile修飾的int類型state變量和一個CLH隊列Craig, Landin, and Hagersten lock queue的變種。這個state在不同子類中有不同語義——在ReentrantLock中表示重入次數(shù)在Semaphore中表示剩余許可數(shù)。CLH隊列則是所有等待線程的排隊通道采用雙向鏈表實現(xiàn)每個節(jié)點保存著線程引用和等待狀態(tài)。關鍵洞察AQS采用了模板方法模式將acquire/release等操作定義為final方法子類只需實現(xiàn)tryAcquire/tryRelease等鉤子方法。這種設計讓ReentrantLock只需關注鎖獲取與釋放的邏輯排隊、阻塞等復雜操作都由AQS統(tǒng)一處理。ReentrantLock在AQS基礎上實現(xiàn)了公平與非公平兩種策略。非公平鎖在lock()時會直接嘗試CAS修改state失敗后才進入隊列代碼示例final void lock() { if (compareAndSetState(0, 1)) // 非公平鎖的核心競爭邏輯 setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); }而公平鎖則永遠遵循FIFO原則final void lock() { acquire(1); // 直接進入隊列排隊 }2. 鎖的可重入性實現(xiàn)細節(jié)可重入性是ReentrantLock區(qū)別于synchronized之外最重要的特性。在AQS框架下重入計數(shù)通過state變量和exclusiveOwnerThread共同實現(xiàn)。當線程首次獲取鎖時state從0變?yōu)?同一線程再次獲取時state會遞增代碼片段protected final boolean tryAcquire(int acquires) { final Thread current Thread.currentThread(); int c getState(); if (c 0) { if (!hasQueuedPredecessors() compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current getExclusiveOwnerThread()) { // 重入判斷 int nextc c acquires; if (nextc 0) throw new Error(Maximum lock count exceeded); setState(nextc); // 不需要CAS因為只有持有線程能修改 return true; } return false; }重入鎖釋放時必須執(zhí)行相同次數(shù)的unlock()操作。我曾遇到過因釋放次數(shù)不匹配導致的死鎖問題——某段代碼在if分支中l(wèi)ock()卻在多個return前忘記unlock()。這種bug在復雜業(yè)務邏輯中極難定位。避坑指南建議使用try-finally保證鎖釋放ReentrantLock lock new ReentrantLock(); lock.lock(); try { // 臨界區(qū)代碼 } finally { lock.unlock(); // 確保任何情況下都會釋放 }3. 條件變量(Condition)的底層機制ConditionObject是AQS的內(nèi)部類每個Condition實例維護一個獨立的條件隊列。與synchronized的wait/notify相比Condition提供了更精細的等待/通知控制。典型的生產(chǎn)者-消費者模式實現(xiàn)private final ReentrantLock lock new ReentrantLock(); private final Condition notFull lock.newCondition(); private final Condition notEmpty lock.newCondition(); public void put(Object x) throws InterruptedException { lock.lock(); try { while (count items.length) notFull.await(); // 釋放鎖并進入條件隊列 items[putPtr] x; if (putPtr items.length) putPtr 0; count; notEmpty.signal(); // 只喚醒消費者線程 } finally { lock.unlock(); } }條件變量的await()過程涉及復雜的隊列轉移創(chuàng)建節(jié)點加入條件隊列完全釋放鎖考慮重入情況阻塞直到被signal()或中斷重新獲取鎖后繼續(xù)執(zhí)行這個過程中節(jié)點會從條件隊列轉移到CLH隊列。signal()操作只是將節(jié)點移入CLH隊列并不立即喚醒線程——這解釋了為什么有時signal()后線程沒有立即執(zhí)行。4. 性能優(yōu)化與實戰(zhàn)陷阱在百萬級并發(fā)的壓力測試中我發(fā)現(xiàn)非公平鎖的吞吐量比公平鎖高出40%以上。這是因為非公平鎖允許插隊行為減少了線程切換開銷。但這也可能導致線程饑餓——在高并發(fā)場景下某些線程可能長時間獲取不到鎖。鎖的優(yōu)化技巧減小臨界區(qū)范圍只鎖必須同步的代碼段避免鎖嵌套容易引發(fā)死鎖使用tryLock()替代lock()帶超時機制的鎖獲取if (lock.tryLock(100, TimeUnit.MILLISECONDS)) { try { // 臨界區(qū) } finally { lock.unlock(); } } else { // 備選方案 }常見死鎖場景分析交叉鎖線程A持有鎖1請求鎖2線程B持有鎖2請求鎖1自死鎖單線程重復調(diào)用lock()但未實現(xiàn)可重入資源死鎖數(shù)據(jù)庫事務與Java鎖混合使用時出現(xiàn)的互相等待我在排查deadlock found when trying to get lock錯誤時發(fā)現(xiàn)線程轉儲(thread dump)是最有效的工具。JDK自帶的jstack可以生成所有線程的調(diào)用棧信息結合可視化工具如fastthread.io能快速定位死鎖環(huán)。5. AQS的擴展應用模式理解AQS后可以自定義實現(xiàn)各種同步器。比如實現(xiàn)一個簡單的二元閉鎖class BinaryLatch extends AbstractQueuedSynchronizer { protected int tryAcquireShared(int ignore) { return (getState() 1) ? 1 : -1; } protected boolean tryReleaseShared(int ignore) { setState(1); return true; } public void signal() { releaseShared(1); } public void await() throws InterruptedException { acquireSharedInterruptibly(1); } }這種模式也被用于CountDownLatch、Semaphore等JUC組件。AQS的共享模式與獨占模式為不同場景提供了靈活的實現(xiàn)基礎。在Java 8環(huán)境下AQS的實現(xiàn)有一些微優(yōu)化比如取消了頭節(jié)點的偽喚醒通過引入status字段。但核心機制保持不變這也是為什么基于Java 8的代碼在不同版本間保持良好兼容性。對于could not acquire lock(s)這類錯誤除了檢查死鎖外還需要注意鎖泄漏忘記釋放鎖導致其他線程永久等待鎖粒度過粗的鎖設計導致性能瓶頸鎖分段ConcurrentHashMap式的分段鎖優(yōu)化方案在Oracle 19c等數(shù)據(jù)庫中表級鎖LOCK TABLE有不同模式如行共享、行獨占這與ReentrantLock的公平/非公平策略有異曲同工之妙。理解這些底層機制能幫助我們在不同層級設計更優(yōu)的并發(fā)控制方案。