AQS 全稱是 AbstractQueuedSynchronizer,顧名思義,是一個(gè)用來(lái)構(gòu)建鎖和同步器的框架,它底層用了 CAS 技術(shù)來(lái)保證操作的原子性,同時(shí)利用 FIFO 隊(duì)列實(shí)現(xiàn)線程間的鎖競(jìng)爭(zhēng),將基礎(chǔ)的同步相關(guān)抽象細(xì)節(jié)放在 AQS,這也是 ReentrantLock、CountDownLatch 等同步工具實(shí)現(xiàn)同步的底層實(shí)現(xiàn)機(jī)制。它能夠成為實(shí)現(xiàn)大部分同步需求的基礎(chǔ),也是 J.U.C 并發(fā)包同步的核心基礎(chǔ)組件。
AQS 結(jié)構(gòu)剖析
AQS 就是建立在 CAS 的基礎(chǔ)之上,增加了大量的實(shí)現(xiàn)細(xì)節(jié),例如獲取同步狀態(tài)、FIFO 同步隊(duì)列,獨(dú)占式鎖和共享式鎖的獲取和釋放等等,這些都是 AQS 類對(duì)于同步操作抽離出來(lái)的一些通用方法,這么做也是為了對(duì)實(shí)現(xiàn)的一個(gè)同步類屏蔽了大量的細(xì)節(jié),大大降低了實(shí)現(xiàn)同步工具的工作量,這也是為什么 AQS 是其它許多同步類的基類的原因。
現(xiàn)在我們來(lái)直接定位到類 java.util.concurrent.locks.AbstractQueuedSynchronizer,下面是 AQS 類的幾個(gè)重要字段與方法列出來(lái):
publicabstractclassAbstractQueuedSynchronizerextendsAbstractOwnableSynchronizerimplementsjava.io.Serializable{privatetransientvolatileNode head;privatetransientvolatileNode tail;privatevolatileintstate;protectedfinalbooleancompareAndSetState(intexpect,intupdate){// See below for intrinsics setup to support thisreturnunsafe.compareAndSwapInt(this, stateOffset, expect, update);? ? }// ...}
head 字段為等待隊(duì)列的頭節(jié)點(diǎn),表示當(dāng)前正在執(zhí)行的節(jié)點(diǎn);
tail 字段為等待隊(duì)列的尾節(jié)點(diǎn);
state 字段為同步狀態(tài),其中 state > 0 為有鎖狀態(tài),每次加鎖就在原有 state 基礎(chǔ)上加 1,即代表當(dāng)前持有鎖的線程加了 state 次鎖,反之解鎖時(shí)每次減一,當(dāng) statte = 0 為無(wú)鎖狀態(tài);
通過(guò) compareAndSetState 方法操作 CAS 更改 state 狀態(tài),保證 state 的原子性。
有沒(méi)有發(fā)現(xiàn),這幾個(gè)字段都用 volatile 關(guān)鍵字進(jìn)行修飾,以確保多線程間保證字段的可見(jiàn)性。
AQS 提供了兩種鎖,分別是獨(dú)占鎖和共享鎖,獨(dú)占鎖指的是操作被認(rèn)作一種獨(dú)占操作,比如 ReentrantLock,它實(shí)現(xiàn)了獨(dú)占鎖的方法,而共享鎖則指的是一個(gè)非獨(dú)占操作,比如一些同步工具 CountDownLatch 和 Semaphore 等同步工具,下面是 AQS 對(duì)這兩種鎖提供的抽象方法。
獨(dú)占鎖:
// 獲取鎖方法protectedbooleantryAcquire(intarg){thrownewUnsupportedOperationException();}// 釋放鎖方法protectedbooleantryRelease(intarg){thrownewUnsupportedOperationException();}
共享鎖:
// 獲取鎖方法protectedinttryAcquireShared(intarg){thrownewUnsupportedOperationException();}// 釋放鎖方法protectedbooleantryReleaseShared(intarg){thrownewUnsupportedOperationException();}
在我們平時(shí)開(kāi)發(fā)中,基本不用直接使用 AQS,我們平時(shí)都是直接使用 JDK 自帶的同步類工具,如 ReentrantLock、CountDownLatch 和 Semaphore 等,它們已經(jīng)可以滿足絕大部分的需求了,后面會(huì)抽幾篇文章單獨(dú)講一下這些同步類工具是如何使用 AQS 的,這對(duì)于我們?nèi)绾螛?gòu)建自定義的同步工具,有很大的幫助。
下面是同步隊(duì)列節(jié)點(diǎn)的結(jié)構(gòu):
用大神的注釋來(lái)形象地描述一下隊(duì)列的模型:
/**? *
? *? ? ? +------+? prev +-----+? ? ? +-----+* head |? ? ? | <---- |? ? | <---- |? ? |? tail*? ? ? +------+? ? ? +-----+? ? ? +-----+*
? */
這是一個(gè)普通雙向鏈表的節(jié)點(diǎn)結(jié)構(gòu),多了 thread 字段用于存儲(chǔ)當(dāng)前線程對(duì)象,同時(shí)每個(gè)節(jié)點(diǎn)都有一個(gè) waitStatus 等待狀態(tài),一共有四種狀態(tài):
CANCELLED(1):取消狀態(tài),如果當(dāng)前線程的前置節(jié)點(diǎn)狀態(tài)為 CANCELLED,則表明前置節(jié)點(diǎn)已經(jīng)等待超時(shí)或者已經(jīng)被中斷了,這時(shí)需要將其從等待隊(duì)列中刪除。
SIGNAL(-1):等待觸發(fā)狀態(tài),如果當(dāng)前線程的前置節(jié)點(diǎn)狀態(tài)為 SIGNAL,則表明當(dāng)前線程需要阻塞。
CONDITION(-2):等待條件狀態(tài),表示當(dāng)前節(jié)點(diǎn)在等待 condition,即在 condition 隊(duì)列中。
PROPAGATE(-3):狀態(tài)需要向后傳播,表示 releaseShared 需要被傳播給后續(xù)節(jié)點(diǎn),僅在共享鎖模式下使用。
可以這么理解:head 節(jié)點(diǎn)可以表示成當(dāng)前持有鎖的線程的節(jié)點(diǎn),其余線程競(jìng)爭(zhēng)鎖失敗后,會(huì)加入到隊(duì)尾,tail 始終指向隊(duì)列的最后一個(gè)節(jié)點(diǎn)。
AQS 的結(jié)構(gòu)大概可總結(jié)為以下 3 部分:
用 volatile 修飾的整數(shù)類型的 state 狀態(tài),用于表示同步狀態(tài),提供 getState 和 setState 來(lái)操作同步狀態(tài);
提供了一個(gè) FIFO 等待隊(duì)列,實(shí)現(xiàn)線程間的競(jìng)爭(zhēng)和等待,這是 AQS 的核心;
AQS 內(nèi)部提供了各種基于 CAS 原子操作方法,如 compareAndSetState 方法,并且提供了鎖操作的acquire和release方法。
獨(dú)占鎖
獨(dú)占鎖的原理是如果有線程獲取到鎖,那么其它線程只能是獲取鎖失敗,然后進(jìn)入等待隊(duì)列中等待被喚醒。
進(jìn)群:697699179可以獲取Java各類入門(mén)學(xué)習(xí)資料!
這是我的微信公眾號(hào)【編程study】各位大佬有空可以關(guān)注下,每天更新Java學(xué)習(xí)方法,感謝!
學(xué)習(xí)中遇到問(wèn)題有不明白的地方,推薦加小編Java學(xué)習(xí)群:697699179內(nèi)有視頻教程 ,直播課程 ,等學(xué)習(xí)資料,期待你的加入
獲取鎖
獲取獨(dú)占鎖方法:
publicfinalvoidacquire(intarg){if(!tryAcquire(arg) &&? ? ? acquireQueued(addWaiter(Node.EXCLUSIVE), arg))? ? selfInterrupt();}
源碼解讀:
通過(guò) tryAcquire(arg) 方法嘗試獲取鎖,這個(gè)方法需要實(shí)現(xiàn)類自己實(shí)現(xiàn)獲取鎖的邏輯,獲取鎖成功后則不執(zhí)行后面加入等待隊(duì)列的邏輯了;
如果嘗試獲取鎖失敗后,則執(zhí)行 addWaiter(Node.EXCLUSIVE) 方法將當(dāng)前線程封裝成一個(gè) Node 節(jié)點(diǎn)對(duì)象,并加入隊(duì)列尾部;
把當(dāng)前線程執(zhí)行封裝成 Node 節(jié)點(diǎn)后,繼續(xù)執(zhí)行 acquireQueued 的邏輯,該邏輯主要是判斷當(dāng)前節(jié)點(diǎn)的前置節(jié)點(diǎn)是否是頭節(jié)點(diǎn),來(lái)嘗試獲取鎖,如果獲取鎖成功,則當(dāng)前節(jié)點(diǎn)就會(huì)成為新的頭節(jié)點(diǎn),這也是獲取鎖的核心邏輯。
基于上面源碼的步驟分析后,我們一步步往下看源碼具體實(shí)現(xiàn):
privateNode addWaiter(Node mode) {// 創(chuàng)建一個(gè)基于當(dāng)前線程的節(jié)點(diǎn),該節(jié)點(diǎn)是 Node.EXCLUSIVE 獨(dú)占式類型Node node = new Node(Thread.currentThread(), mode);// Try the fast path of enq; backup to full enq on failureNode pred = tail;// 這里先判斷隊(duì)尾是否為空,如果不為空則直接將節(jié)點(diǎn)加入隊(duì)尾if(pred !=null) {? ? node.prev = pred;// 采取 CAS 操作,將當(dāng)前節(jié)點(diǎn)設(shè)置為隊(duì)尾節(jié)點(diǎn),由于采用了 CAS 原子操作,無(wú)論并發(fā)怎么修改,都有且只有一條線程可以修改成功,其余都將執(zhí)行后面的enq方法if(compareAndSetTail(pred, node)) {? ? ? pred.next = node;returnnode;? ? }? }? enq(node);returnnode;}
簡(jiǎn)單來(lái)說(shuō) addWaiter(Node mode) 方法做了以下事情:
創(chuàng)建基于當(dāng)前線程的獨(dú)占式類型的節(jié)點(diǎn);
利用 CAS 原子操作,將節(jié)點(diǎn)加入隊(duì)尾。
我們繼續(xù)看 enq(Node node) 方法:
privateNodeenq(finalNode node){// 自旋操作for(;;) {? ? Node t = tail;// 如果隊(duì)尾節(jié)點(diǎn)為空,那么進(jìn)行CAS操作初始化隊(duì)列if(t ==null) {// 這里很關(guān)鍵,即如果隊(duì)列為空,那么此時(shí)必須初始化隊(duì)列,初始化一個(gè)空的節(jié)點(diǎn)表示隊(duì)列頭,用于表示當(dāng)前正在執(zhí)行的節(jié)點(diǎn),頭節(jié)點(diǎn)即表示當(dāng)前正在運(yùn)行的節(jié)點(diǎn)if(compareAndSetHead(newNode()))? ? ? ? tail = head;? ? }else{? ? ? node.prev = t;// 這一步也是采取CAS操作,將當(dāng)前節(jié)點(diǎn)加入隊(duì)尾,如果失敗的話,自旋繼續(xù)修改直到成功為止if(compareAndSetTail(t, node)) {? ? ? ? t.next = node;returnt;? ? ? }? ? }? }}
enq(final Node node) 方法主要做了以下事情:
采用自旋機(jī)制,這是 aqs 里面很重要的一個(gè)機(jī)制;
如果隊(duì)尾節(jié)點(diǎn)為空,則初始化隊(duì)列,將頭節(jié)點(diǎn)設(shè)置為空節(jié)點(diǎn),頭節(jié)點(diǎn)即表示當(dāng)前正在運(yùn)行的節(jié)點(diǎn);
如果隊(duì)尾節(jié)點(diǎn)不為空,則繼續(xù)采取 CAS 操作,將當(dāng)前節(jié)點(diǎn)加入隊(duì)尾,不成功則繼續(xù)自旋,直到成功為止;
對(duì)比了上面兩段代碼,不難看出,首先是判斷隊(duì)尾是否為空,先進(jìn)行一次 CAS 入隊(duì)操作,如果失敗則進(jìn)入 enq(final Node node) 方法執(zhí)行完整的入隊(duì)操作。
完整的入隊(duì)操作簡(jiǎn)單來(lái)說(shuō)就是:?如果隊(duì)列為空,初始化隊(duì)列,并將頭節(jié)點(diǎn)設(shè)為空節(jié)點(diǎn),表示當(dāng)前正在運(yùn)行的節(jié)點(diǎn),然后再將當(dāng)前線程的節(jié)點(diǎn)加入到隊(duì)列尾部。
關(guān)于隊(duì)列的初始化與入隊(duì),務(wù)必理解透徹。
經(jīng)過(guò)上面 CAS 不斷嘗試,這時(shí)當(dāng)前節(jié)點(diǎn)已經(jīng)成功加入到隊(duì)尾了,接下來(lái)就到了acquireQueued 的邏輯,我們繼續(xù)往下看源碼:
finalbooleanacquireQueued(finalNode node,intarg){booleanfailed =true;try{// 線程中斷標(biāo)記字段booleaninterrupted =false;for(;;) {// 獲取當(dāng)前節(jié)點(diǎn)的 pred 節(jié)點(diǎn)finalNode p = node.predecessor();// 如果 pred 節(jié)點(diǎn)為 head 節(jié)點(diǎn),那么再次嘗試獲取鎖if(p == head && tryAcquire(arg)) {// 獲取鎖之后,那么當(dāng)前節(jié)點(diǎn)也就成為了 head 節(jié)點(diǎn)setHead(node);? ? ? ? p.next =null;// help GCfailed =false;// 不需要掛起,返回 falsereturninterrupted;? ? ? }// 獲取鎖失敗,則進(jìn)入掛起邏輯if(shouldParkAfterFailedAcquire(p, node) &&? ? ? ? ? parkAndCheckInterrupt())? ? ? ? interrupted =true;? ? }? }finally{if(failed)? ? ? cancelAcquire(node);? }}
這一步 acquireQueued(final Node node, int arg) 方法主要做了以下事情:
判斷當(dāng)前節(jié)點(diǎn)的 pred 節(jié)點(diǎn)是否為 head 節(jié)點(diǎn),如果是,則嘗試獲取鎖;
獲取鎖失敗后,進(jìn)入掛起邏輯。
提醒一點(diǎn):?我們上面也說(shuō)過(guò),head 節(jié)點(diǎn)代表當(dāng)前持有鎖的線程,那么如果當(dāng)前節(jié)點(diǎn)的 pred 節(jié)點(diǎn)是 head 節(jié)點(diǎn),很可能此時(shí) head 節(jié)點(diǎn)已經(jīng)釋放鎖了,所以此時(shí)需要再次嘗試獲取鎖。
接下來(lái)繼續(xù)看掛起邏輯源碼:
privatestaticbooleanshouldParkAfterFailedAcquire(Node pred, Node node) {intws = pred.waitStatus;if(ws == Node.SIGNAL)// 如果 pred 節(jié)點(diǎn)為 SIGNAL 狀態(tài),返回true,說(shuō)明當(dāng)前節(jié)點(diǎn)需要掛起returntrue;// 如果ws > 0,說(shuō)明節(jié)點(diǎn)狀態(tài)為CANCELLED,需要從隊(duì)列中刪除if(ws >0) {do{? ? ? node.prev = pred = pred.prev;? ? }while(pred.waitStatus >0);? ? pred.next= node;? }else{// 如果是其它狀態(tài),則操作CAS統(tǒng)一改成SIGNAL狀態(tài)// 由于這里waitStatus的值只能是0或者PROPAGATE,所以我們將節(jié)點(diǎn)設(shè)置為SIGNAL,從新循環(huán)一次判斷compareAndSetWaitStatus(pred, ws, Node.SIGNAL);? }returnfalse;}
這一步 shouldParkAfterFailedAcquire(Node pred, Node node) 方法主要做了以下事情:
判斷 pred 節(jié)點(diǎn)狀態(tài),如果為 SIGNAL 狀態(tài),則直接返回 true 執(zhí)行掛起;
刪除狀態(tài)為 CANCELLED 的節(jié)點(diǎn);
若 pred 節(jié)點(diǎn)狀態(tài)為 0 或者 PROPAGATE,則將其設(shè)置為為 SIGNAL,再?gòu)?acquireQueued 方法自旋操作從新循環(huán)一次判斷。
通俗來(lái)說(shuō)就是:根據(jù) pred 節(jié)點(diǎn)狀態(tài)來(lái)判斷當(dāng)前節(jié)點(diǎn)是否可以掛起,如果該方法返回 false,那么掛起條件還沒(méi)準(zhǔn)備好,就會(huì)重新進(jìn)入 acquireQueued(final Node node, int arg) 的自旋體,重新進(jìn)行判斷。如果返回 true,那就說(shuō)明當(dāng)前線程可以進(jìn)行掛起操作了,那么就會(huì)繼續(xù)執(zhí)行掛起。
這里需要注意的時(shí)候,節(jié)點(diǎn)的初始值為 0,因此如果獲取鎖失敗,會(huì)嘗試將節(jié)點(diǎn)設(shè)置為 SIGNAL。
繼續(xù)看掛起邏輯:
privatefinalboolean parkAndCheckInterrupt() {? LockSupport.park(this);returnThread.interrupted();}
LockSupport 是用來(lái)創(chuàng)建鎖和其他同步類的基本?線程阻塞?原語(yǔ)。LockSupport 提供 park() 和 unpark() 方法實(shí)現(xiàn)阻塞線程和解除線程阻塞。release 釋放鎖方法邏輯會(huì)調(diào)用 LockSupport.unPark 方法來(lái)喚醒后繼節(jié)點(diǎn)。
獲取獨(dú)占鎖流程圖:
釋放鎖
釋放鎖方法:
publicfinalbooleanrelease(intarg){if(tryRelease(arg)) {? ? Node h = head;if(h !=null&& h.waitStatus !=0)? ? ? unparkSuccessor(h);returntrue;? }returnfalse;}
釋放鎖的方法源碼就很好理解,通過(guò) tryRelease(arg) 方法嘗試釋放鎖,這個(gè)方法需要實(shí)現(xiàn)類自己實(shí)現(xiàn)釋放鎖的邏輯,釋放鎖成功后則執(zhí)行后面的喚醒后續(xù)節(jié)點(diǎn)的邏輯了,然后判斷 head 節(jié)點(diǎn)不為空并且 head 節(jié)點(diǎn)狀態(tài)不為 0,因?yàn)?addWaiter 方法默認(rèn)的節(jié)點(diǎn)狀態(tài)為 0,此時(shí)節(jié)點(diǎn)還沒(méi)有進(jìn)入就緒狀態(tài)。
繼續(xù)往下看源碼:
privatevoidunparkSuccessor(Node node){intws = node.waitStatus;if(ws <0)// 將頭節(jié)點(diǎn)的狀態(tài)設(shè)置為0// 這里會(huì)嘗試清除頭節(jié)點(diǎn)的狀態(tài),改為初始狀態(tài)compareAndSetWaitStatus(node, ws,0);// 后繼節(jié)點(diǎn)Node s = node.next;// 如果后繼節(jié)點(diǎn)為null,或者已經(jīng)被取消了if(s ==null|| s.waitStatus >0) {? ? s =null;// for循環(huán)從隊(duì)列尾部一直往前找可以喚醒的節(jié)點(diǎn)for(Node t = tail; t !=null&& t != node; t = t.prev)if(t.waitStatus <=0)? ? ? ? s = t;? }if(s !=null)// 喚醒后繼節(jié)點(diǎn)LockSupport.unpark(s.thread);}
從源碼可看出:?釋放鎖主要是將頭節(jié)點(diǎn)的后繼節(jié)點(diǎn)喚醒,如果后繼節(jié)點(diǎn)不符合喚醒條件,則從隊(duì)尾一直往前找,直到找到符合條件的節(jié)點(diǎn)為止?。
總結(jié)
這篇文章主要講述了 AQS 的內(nèi)部結(jié)構(gòu)和它的同步實(shí)現(xiàn)原理,并從源碼的角度深度剖析了 AQS 獨(dú)占鎖模式下的獲取鎖與釋放鎖的邏輯,并且從源碼中我們得出:?在獨(dú)占鎖模式下,用 state 值表示鎖并且 0 表示無(wú)鎖狀態(tài),0 -> 1 表示從無(wú)鎖到有鎖,僅允許一條線程持有鎖,其余的線程會(huì)被包裝成一個(gè) Node 節(jié)點(diǎn)放到隊(duì)列中進(jìn)行掛起,隊(duì)列中的頭節(jié)點(diǎn)表示當(dāng)前正在執(zhí)行的線程,當(dāng)頭節(jié)點(diǎn)釋放后會(huì)喚醒后繼節(jié)點(diǎn),從而印證了 AQS 的隊(duì)列是一個(gè) FIFO 同步隊(duì)列。