AQS 原理
1. 前言
本節(jié)內(nèi)容主要是對 AQS 原理的講解,之所以需要了解 AQS 原理,是因?yàn)楹罄m(xù)講解的 ReentrantLock 是基于 AQS 原理的。本節(jié)內(nèi)容相較于其他小節(jié)難度上會大一些,基礎(chǔ)薄弱的學(xué)習(xí)者可以選擇性學(xué)習(xí)本節(jié)內(nèi)容或者跳過本節(jié)內(nèi)容。
- 了解什么是 AQS,這是認(rèn)識 AQS 原理的前提,是本節(jié)的基礎(chǔ)知識點(diǎn);
- 了解 AQS 提供的兩種鎖功能,對其有一個全局的了解;
- 了解 AQS 的內(nèi)部框架原理結(jié)構(gòu),這是本節(jié)課程的核心所在,其他所有的知識點(diǎn)講解都是圍繞這一知識點(diǎn)的;
- 釋放鎖以及添加線程對于 AQS 內(nèi)部的變化,這是本節(jié)課程的重點(diǎn)知識,了解隊(duì)列的學(xué)習(xí)者能夠更快的掌握這部分知識;
- AQS 與 ReentrantLock 的聯(lián)系,這是本節(jié)課程與 ReentrantLock 之間的過度知識。
2. 什么是 AQS
定義:AbstarctQueuedSynchronizer 簡稱 AQS,是一個用于構(gòu)建鎖和同步容器的框架。
事實(shí)上 concurrent 包內(nèi)許多類都是基于 AQS 構(gòu)建的,例如 ReentrantLock,ReentrantReadWriteLock,F(xiàn)utureTask 等。AQS 解決了在實(shí)現(xiàn)同步容器時大量的細(xì)節(jié)問題。
AQS 使用一個 FIFO 隊(duì)列表示排隊(duì)等待鎖的線程,隊(duì)列頭結(jié)點(diǎn)稱作 “哨兵節(jié)點(diǎn)” 或者 “啞結(jié)點(diǎn)”,它不與任何線程關(guān)聯(lián)。其他的節(jié)點(diǎn)與等待線程關(guān)聯(lián),每個階段維護(hù)一個等待狀態(tài) waitStatus。
3. AQS 提供的兩種功能
從使用層面來說,AQS 的鎖功能分為兩種:獨(dú)占鎖和共享鎖。
獨(dú)占鎖:每次只能有一個線程持有鎖,比如前面給大家演示的 ReentrantLock 就是以獨(dú)占方式實(shí)現(xiàn)的互斥鎖;
共享鎖:允許多個線程同時獲取鎖,并發(fā)訪問共享資源,比如 ReentrantReadWriteLock。
4. AQS 的內(nèi)部實(shí)現(xiàn)
AQS 的實(shí)現(xiàn)依賴內(nèi)部的同步隊(duì)列,也就是 FIFO 的雙向隊(duì)列,如果當(dāng)前線程競爭鎖失敗,那么 AQS 會把當(dāng)前線程以及等待狀態(tài)信息構(gòu)造成一個 Node 加入到同步隊(duì)列中,同時再阻塞該線程。當(dāng)獲取鎖的線程釋放鎖以后,會從隊(duì)列中喚醒一個阻塞的節(jié)點(diǎn) (線程)。
如下圖所示,一個節(jié)點(diǎn)表示一個線程,它保存著線程的引用(thread)、狀態(tài)(waitStatus)、前驅(qū)節(jié)點(diǎn)(prev)、后繼節(jié)點(diǎn)(next),其實(shí)就是個雙端雙向鏈表,其數(shù)據(jù)結(jié)構(gòu)如下:
Tips:AQS 隊(duì)列內(nèi)部維護(hù)的是一個 FIFO 的雙向鏈表,這種結(jié)構(gòu)的特點(diǎn)是每個數(shù)據(jù)結(jié)構(gòu)都有兩個指針,分別指向直接的后繼節(jié)點(diǎn)和直接前驅(qū)節(jié)點(diǎn)。所以雙向鏈表可以從任意一個節(jié)點(diǎn)開始,很方便的訪問前驅(qū)和后繼。每個 Node 其實(shí)是由線程封裝,當(dāng)線程爭搶鎖失敗后會封裝成 Node 加入到 ASQ 隊(duì)列中去。
5. 添加線程對于 AQS 隊(duì)列的變化
當(dāng)出現(xiàn)鎖競爭以及釋放鎖的時候,AQS 同步隊(duì)列中的節(jié)點(diǎn)會發(fā)生變化,首先看一下添加線程的場景。
這里會涉及到兩個變化:
- 隊(duì)列操作的變化:新的線程封裝成 Node 節(jié)點(diǎn)追加到同步隊(duì)列中,設(shè)置 prev 節(jié)點(diǎn)以及修改當(dāng)前節(jié)點(diǎn)的前置節(jié)點(diǎn)的 next 節(jié)點(diǎn)指向自己;
- tail 指向變化:通過同步器將 tail 重新指向新的尾部節(jié)點(diǎn)。
6. 釋放鎖移除節(jié)點(diǎn)對于 AQS 隊(duì)列的變化
第一個 head 節(jié)點(diǎn)表示獲取鎖成功的節(jié)點(diǎn),當(dāng)頭結(jié)點(diǎn)在釋放同步狀態(tài)時,會喚醒后繼節(jié)點(diǎn),如果后繼節(jié)點(diǎn)獲得鎖成功,會把自己設(shè)置為頭結(jié)點(diǎn),節(jié)點(diǎn)的變化過程如下:
這個過程也是涉及到兩個變化:
head 節(jié)點(diǎn)指向:修改 head 節(jié)點(diǎn)指向下一個獲得鎖的節(jié)點(diǎn);
新的獲得鎖的節(jié)點(diǎn):如圖所示,第二個節(jié)點(diǎn)被 head 指向了,此時將 prev 的指針指向 null,因?yàn)樗约罕旧砭褪堑谝粋€首節(jié)點(diǎn),所以 pre 指向 null。
7. AQS 與 ReentrantLock 的聯(lián)系
ReentrantLock 實(shí)現(xiàn):ReentrantLock 是根據(jù) AQS 實(shí)現(xiàn)的獨(dú)占鎖,提供了兩個構(gòu)造方法如下:
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
ReentrantLock 有三個內(nèi)部類:Sync,NonfairSync,F(xiàn)airSync,繼承關(guān)系如下:
總結(jié):我們可以看到,這三個內(nèi)部類都是基于 AQS 進(jìn)行的實(shí)現(xiàn),由此可見,ReentrantLock 是基于 AQS 進(jìn)行的實(shí)現(xiàn)。
ReentrantLock 提供兩種類型的鎖:公平鎖,非公平鎖。分別對應(yīng) FairSync,NonfairSync。默認(rèn)實(shí)現(xiàn)是 NonFairSync。
8. 小結(jié)
本節(jié)內(nèi)容為 AQS 原理進(jìn)行講解,會涉及到一些原理問題,隊(duì)列問題,基礎(chǔ)薄弱的學(xué)習(xí)者可以跳過或者選看本節(jié)內(nèi)容,不會影響后續(xù)課程的學(xué)習(xí)。本節(jié)內(nèi)容其實(shí)主要為了提供原理性的知識,對本節(jié)的知識掌握,使我們不僅僅是一個使用者。