【AQS面試篇】瞭解ReentrantLock嗎?講講其底層實現
AQS知識點
1. ReentrantLock和AQS的關係
Lock的使用:
Lock lock = new ReentrantLock();
// 加鎖
lock.lock();
try {
// 執行緒任務
} catch (Exception e) {
e.printStackTrace();
} finally {
// 釋放鎖
lock.unlock();
}
ReentrantLock
、ReentrantReadWriteLock
底層,是基於AQS來實現的
AQS(AbstractQueuedSynchronizer),抽象佇列同步器。ReentrantLock
內部包含了AQS物件,AQS是ReentrantLock
實現加鎖和釋放鎖的關鍵核心元件
AQS 佇列同步器 三要素 :
- CAS 通過此來競爭鎖資源
- LockSurpport.park/unpark 執行緒阻塞和釋放
- CLH 雙向連結串列 存放被阻塞的執行緒
AQS原理
AQS:AbstractQuenedSynchronizer抽象的佇列式同步器。是除了java自帶的synchronized關鍵字之外的鎖機制。AQS的全稱為(AbstractQueuedSynchronizer),這個類在java.util.concurrent.locks包
AQS的核心思想:
如果被請求的共享資源空閒,則將當前請求資源的執行緒設定為有效的工作執行緒,並將共享資源設定為鎖定狀態,如果被請求的共享資源被佔用,那麼就需要一套執行緒阻塞等待以及被喚醒時鎖分配的機制,這個機制AQS是用CLH佇列鎖實現的,即將暫時獲取不到鎖的執行緒加入到佇列中。
用大白話來說,AQS就是基於CLH佇列,用volatile修飾共享變數state,執行緒通過CAS去改變狀態符,成功則獲取鎖成功,失敗則進入等待佇列,等待被喚醒。
-
CLH(Craig,Landin,and Hagersten)佇列是一個虛擬的雙向佇列,虛擬的雙向佇列即不存在佇列例項,僅存在節點之間的關聯關係。
-
AQS是將每一條請求共享資源的執行緒封裝成一個CLH鎖佇列的一個結點(Node),來實現鎖的分配
AQS維護了一個volatile int state
和一個FIFO執行緒等待佇列,多執行緒爭用資源被阻塞的時候就會進入這個佇列。
state就是共享資源,其訪問方式有如下三種:
getState()
setState()
compareAndSetState();
CLH佇列–雙向連結串列實現
AQS Node節點被 volatile
修飾,head 和 tail(即:連結串列的頭部和尾部)
// Head of the wait queue, lazily initialized.
private transient volatile Node head;
// Tail of the wait queue, lazily initialized. .
private transient volatile Node tail;
注意:AQS是自旋鎖: 在等待喚醒的時候,經常會使用自旋while(!cas())
的方式,不停地嘗試獲取鎖,直到被其他執行緒獲取成功
實現了AQS的鎖有:自旋鎖、互斥鎖、讀鎖寫鎖、條件產量、訊號量、柵欄都是AQS的衍生物
2. ReentrantLock鎖機制原理
AQS內部核心
state
int型別,初始為0。代表加鎖的狀態- 關鍵變數,記錄當前獲取到鎖的執行緒,初值為null
初始時,執行緒A呼叫RentrantLock的lock
方法嘗試加鎖,加鎖的過程是用CAS將state
由0變為1
此時執行緒A獲取鎖成功
執行緒A加鎖成功後,將加鎖執行緒設定為自己
可重入鎖
執行緒A再次獲取到了鎖,判斷當前加鎖執行緒是否是自己?,是的話再次獲取鎖成功,satte
類加1
互斥鎖
執行緒B想要獲取鎖,先判斷state
是否為0
state = 0
,嘗試獲取鎖state != 0
,看持有鎖的執行緒是否是自己;不是則進入到等待佇列- 執行緒B等待執行緒A釋放鎖之後,嘗試重新獲取
AQS中有一個等待佇列,存放獲取鎖?失敗的執行緒
釋放鎖
state
變數減一,為0時徹底釋放鎖,不再持有資源,直至 加鎖執行緒為null
接下來,從等待佇列的對頭喚醒執行緒B,重新嘗試加鎖
- 執行緒B重複上述操作,獲取鎖成功
3. AQS資源共享方式
AQS 定義了兩種資源共享方式:
- Exclusive:獨佔,只有一個執行緒能執行,如ReentrantLock
- Share:共享,多個執行緒可以同時執行,如Semaphore、CountDownLatch、ReadWriteLock,CyclicBarrier
不同的自定義的同步器爭用共享資源的方式也不同
4. AQS底層使用了模板方法模式
同步器的設計是基於模板方法模式的,如果需要自定義同步器一般的方式是這樣(模板方法模式很經典的一個應用):
- 使用者繼承AbstractQueuedSynchronizer並重寫指定的方法。
- 將AQS組合在自定義同步元件的實現中,並呼叫其模板方法,而這些模板方法會呼叫使用者重寫的方法。
這和我們以往通過實現介面的方式有很大區別,這是模板方法模式很經典的一個運用。
自定義同步器在實現的時候只需要實現共享資源state的獲取和釋放方式即可,至於具體執行緒等待佇列的維護,AQS已經在頂層實現好了。自定義同步器實現的時候主要實現下面幾種方法:
isHeldExclusively()
:該執行緒是否正在獨佔資源。只有用到Condition
才需要去實現它;
獨佔
tryAcquire(int)
:獨佔方式。嘗試獲取資源,成功則返回true,失敗則返回false;tryRelease(int)
:獨佔方式。嘗試釋放資源,成功則返回true,失敗則返回false;
共享
tryAcquireShared(int)
:共享方式。嘗試獲取資源。負數表示失敗;0表示成功,但沒有剩餘可用資源;正數表示成功,且有剩餘資源;tryReleaseShared(int)
:共享方式。嘗試釋放資源,如果釋放後允許喚醒後續等待結點返回true,否則返回false;
ReentrantLock為例
(可重入獨佔式鎖):state初始化為0,表示未鎖定狀態,A執行緒lock()時,會呼叫tryAcquire()獨佔鎖並將state+1.之後其他執行緒再想tryAcquire的時候就會失敗,直到A執行緒unlock()到state=0為止,其他執行緒才有機會獲取該鎖。A釋放鎖之前,自己也是可以重複獲取此鎖(state累加),這就是可重入的概念。
注意:獲取多少次鎖就要釋放多少次鎖,保證state是能回到零態的
CountDownLatch為例
任務分N個子執行緒去執行,state就初始化 為N,N個執行緒並行執行,每個執行緒執行完之後countDown()
一次,state就會CAS減一。當N子執行緒全部執行完畢,state=0,會unpark()
主呼叫執行緒,主呼叫執行緒就會從await()
函式返回,繼續之後的動作。
一般來說,自定義同步器要麼是獨佔方法,要麼是共享方式,他們也只需實現tryAcquire-tryRelease
、tryAcquireShared-tryReleaseShared
中的一種即可。但AQS也支援自定義同步器同時實現獨佔和共享兩種方式,如ReentrantReadWriteLock。
部分內容參考自:
相關文章
- 面試官: 有了解過ReentrantLock的底層實現嗎?說說看面試ReentrantLock
- 【Nginx】面試官:給我講講Nginx如何實現四層負載均衡?Nginx面試負載
- 萬字超強圖文講解AQS以及ReentrantLock應用(建議收藏)AQSReentrantLock
- 面經手冊 · 第16篇《碼農會鎖,ReentrantLock之公平鎖講解和實現》ReentrantLock
- 講講Handler實現原理
- 面試題深入解析:Synchronized底層實現面試題synchronized
- ReentrantLock & AQSReentrantLockAQS
- 記錄---nextTick用過嗎?講一講實現思路吧
- LinkedHashMap的實現講解HashMap
- 面試必問:HashMap 底層實現原理分析面試HashMap
- iOS窺探KVO底層實現實戰篇iOS
- 簡單瞭解InnoDB底層原理
- 面試官:說說反射的底層實現原理?面試反射
- 【面試題】能聊聊你對CAS的理解以及其底層實現原理可以嗎?面試題
- iOS窺探KVO底層實現原理篇iOS
- 深入詳細瞭解synchronized底層原理synchronized
- 瞭解ASP.NET底層架構ASP.NET架構
- 你來講講AQS是什麼吧?都是怎麼用的?AQS
- 精講RestTemplate第2篇-多種底層HTTP客戶端類庫的切換RESTHTTP客戶端
- 【面試普通人VS高手系列】ConcurrentHashMap 底層具體實現知道嗎?實現原理是什麼?面試HashMap
- Java講解RPC的基本實現JavaRPC
- 從ReentrantLock詳解AQS原理原始碼解析ReentrantLockAQS原始碼
- 從ReentrantLock加鎖解鎖角度分析AQSReentrantLockAQS
- 面試官:你瞭解Webpack嗎?面試Web
- thinkphp模型層Model、Logic、Service講解PHP模型
- 深入瞭解Redis底層資料結構Redis資料結構
- HashMap底層實現原理HashMap
- NSDictionary底層實現原理
- mysql索引底層實現MySql索引
- AutoreleasePool底層實現原理
- 從ReentrantLock角度解析AQSReentrantLockAQS
- 死磕Synchronized底層實現,面試你還怕什麼?synchronized面試
- 基礎篇:詳解鎖原理,volatile+cas、synchronized的底層實現synchronized
- 詳解 PHP 陣列的底層實現:HashTablePHP陣列
- 三分鐘深入瞭解Spring底層Spring
- MVC 三層架構案例詳細講解MVC架構
- 面試官: 你瞭解前端路由嗎?面試前端路由
- 聊聊JUC包下的底層支撐類-AbstractQueuedSynchronizer(AQS)AQS