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