Java并發(fā)框架中的AQS詳細(xì)解析
前言
之前說(shuō)鎖的升級(jí)的時(shí)候,說(shuō)到了自旋鎖會(huì)空轉(zhuǎn)幾次嘗試等待獲取資源,其實(shí)這一系列的動(dòng)作是有一個(gè)規(guī)范的這個(gè)規(guī)范叫做同步發(fā)生器AbstractQueuedSynchronizer ,簡(jiǎn)稱AQS。
同步發(fā)生器是用來(lái)構(gòu)建鎖用的,可以說(shuō)是Java中同步組件的基礎(chǔ),在Java JDK的JUC包中:java.util.concurrent。
我們常用的ReentrantLock,Semaphore等等用的都是這樣一個(gè)架構(gòu),可以說(shuō)是這些Lock工具的一個(gè)基礎(chǔ)。
AQS基本思想
AQS是一個(gè)隊(duì)列管理器,通過(guò)內(nèi)置的FIFO同步隊(duì)列去管理線程爭(zhēng)奪資源的。
FIFO就是先進(jìn)先出(Frist In First Out)的縮寫。
其實(shí)現(xiàn)核心是用到了CHL同步隊(duì)列,其核心思想就是:把每一個(gè)線程看作一個(gè)節(jié)點(diǎn)Node,然后給這些節(jié)點(diǎn)上加上前驅(qū)指針和后繼指針,這樣一來(lái)就可以用指針把這些線程節(jié)點(diǎn)(Thread Node)連接起來(lái)形成一個(gè)雙向鏈表,或者說(shuō)雙向隊(duì)列。
除此之外,還有一個(gè)同步器節(jié)點(diǎn)(Synchronized Node),用來(lái)管理這些線程節(jié)點(diǎn)。
同步器節(jié)點(diǎn)也有兩個(gè)指針,第一個(gè)指針指向隊(duì)列首節(jié)點(diǎn),第二個(gè)指針指向隊(duì)列尾節(jié)點(diǎn)。
因此同步器節(jié)點(diǎn)是事實(shí)上的頭節(jié)點(diǎn)Head,下圖就是一個(gè)完整的CHL同步隊(duì)列示意圖。
注:CHL是人名簡(jiǎn)稱沒(méi)啥具體意義。
AQS操作同步隊(duì)列
同步隊(duì)列有了,誰(shuí)能拿到資源則是由一個(gè)狀態(tài)變量(state)來(lái)確定的。
當(dāng)state==0時(shí),表示當(dāng)前資源沒(méi)有線程占用;當(dāng)state>=1時(shí),表示當(dāng)前資源已經(jīng)被占用了,其他線程必須等待資源釋放。
假設(shè)有一個(gè)線程要使用資源,首先先會(huì)去檢查state變量獲取結(jié)果,如果state==0說(shuō)明該線程可以使用請(qǐng)求資源,不需要排隊(duì),直接取出線程節(jié)點(diǎn)去執(zhí)行;如果state>=1,說(shuō)明該資源已經(jīng)被前面的線程拿走了,就必須要排隊(duì)。
既然有個(gè)這個(gè)概念,所以說(shuō)每一個(gè)線程節(jié)點(diǎn)都會(huì)做這樣的事情:獲取鎖和釋放鎖。
每個(gè)在隊(duì)列里面的縣城節(jié)點(diǎn),都會(huì)不斷地自旋,每次自旋結(jié)束都會(huì)嘗試獲取鎖,如果獲取不到那么繼續(xù)自旋。
由于是FIFO先進(jìn)先出這種公平模式,因此線程頭節(jié)點(diǎn)總會(huì)第一個(gè)獲取到鎖,以此類推。
這里所有的節(jié)點(diǎn)都在[ 嘗試獲取鎖 – 自旋 ] 這種狀態(tài)不斷地重復(fù)。但是由于使用FIFO模式,只有頭節(jié)點(diǎn)的自旋是有意義的,其他的就是在空轉(zhuǎn)。
這樣做有什么好處呢?假設(shè)我們把所有沒(méi)有獲取到資源的線程都掛起,這就必然要經(jīng)過(guò)用戶線程和核心(系統(tǒng))線程之間的切換,這種切換是非常耗時(shí)的。
由于CPU執(zhí)行的會(huì)很快,所以預(yù)期就是自旋幾次以后,就可以拿到想要的鎖,以規(guī)避線程之間的切換。
AQS 的用法
上面說(shuō)過(guò)AbstractQueuedSynchronizer是一個(gè)框架,它能干什么用還得祭出官方文檔一探究竟,官方文檔很長(zhǎng),我們截取兩句最重要的:
Provides a framework for implementing blocking locks and related synchronizers (semaphores, events, etc) that rely on first-in-first-out (FIFO) wait queues. 這句話是對(duì)AbstractQueuedSynchronizer定義,翻譯過(guò)來(lái)就是說(shuō):為那些想要依賴于FIFO等待隊(duì)列的阻塞鎖和相關(guān)的同步器(semaphores, events, 等等) 提供一個(gè)實(shí)施框架。 Subclasses should be defined as non-public internal helper classes that are used to implement the synchronization properties of their enclosing class. 這句話是用法的概括:其子類應(yīng)該被定義為非共有的內(nèi)部幫助器(助手類),用于實(shí)現(xiàn)外部類的屬性同步。
說(shuō)白了AbstractQueuedSynchronizer就是Java給開發(fā)人員提供一個(gè)獲取鎖和釋放鎖的模板,用來(lái)處理synchronized封鎖粒度過(guò)大的問(wèn)題。
它的主要功能方法如下:
Modifier and Type | Method | Description |
void | acquire(int arg) | Acquires in exclusive mode, ignoring interrupts. 獨(dú)占模式獲取對(duì)象,忽略中斷。 |
void | acquireShared(int arg) | Acquires in shared mode, ignoring interrupts. 共享模式獲取對(duì)象,忽略中斷。 |
boolean | release(int arg) | Releases in exclusive mode.以獨(dú)占模式釋放對(duì)象。 |
boolean | releaseShared(int arg) | Releases in shared mode.以共享模式釋放對(duì)象。 |
protected boolean | tryAcquire(int arg) | Attempts to acquire in exclusive mode.試圖以獨(dú)占模式獲取鎖,這個(gè)就是自旋的方法,一直試探 |
protected int | tryAcquireShared(int arg) | Attempts to acquire in shared mode. 試圖以共享模式獲取鎖。 |
說(shuō)明:共享模式下,當(dāng)一個(gè)線程獲取了鎖,其他線程依然可讀取信息。獨(dú)占模式下,線程獨(dú)占了鎖,不許其他線程使用。
源碼解析AQS
上面已經(jīng)AQS原理和常用方法說(shuō)完了,總要有一個(gè)地方體現(xiàn)吧。我們可以打開一個(gè)方法看看Java中AQS獲取鎖是不是和我們說(shuō)的原理一致。首先進(jìn)入acquire()方法。
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
進(jìn)入以后先判斷tryAcquire(arg)這里面只有一個(gè)拋異常不多說(shuō)了。然后調(diào)用acquireQueued()方法,進(jìn)入。
final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; for (;;) { //空轉(zhuǎn),自旋 final Node p = node.predecessor(); if (p == head && tryAcquire(arg)) { //如果當(dāng)前是頭節(jié)點(diǎn),繼續(xù)嘗試獲取鎖,這也就是為啥只有頭節(jié)點(diǎn)才有意義 setHead(node); p.next = null; // help GC failed = false; return interrupted; } //如果不是繼續(xù)等待 if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } }
發(fā)現(xiàn)這個(gè)方法已經(jīng)要求傳入的參數(shù)就是Node,其實(shí)就是把要等待的Node繼續(xù)等待,那么返回上去,看看addWaiter()又寫了啥。
private Node addWaiter(Node mode) { //把當(dāng)前線程改造稱為Node, Node node = new Node(Thread.currentThread(), mode); // Try the fast path of enq; backup to full enq on failure Node pred = tail; if (pred != null) { node.prev = pred; if (compareAndSetTail(pred, node)) { pred.next = node; return node; } } enq(node); return node; }
進(jìn)入就發(fā)現(xiàn)用構(gòu)建Node用的就是當(dāng)前線程Thread.currentThread(),這就是線程節(jié)點(diǎn)的由來(lái)。
實(shí)現(xiàn)一個(gè)鎖類
在Java的JUC包中同樣為了這樣一個(gè)模板提供了一個(gè)實(shí)現(xiàn)接口就是Lock接口,我們常用的ReentrantLock就是實(shí)現(xiàn)了這個(gè)接口構(gòu)建出來(lái)的,不妨按照這樣一個(gè)模板自己構(gòu)建一個(gè)加深理解。
首先看下Lock接口里面都有什么方法:
Modifier and Type | Method | Description |
void | lock() | Acquires the lock. 獲取鎖。 |
void | lockInterruptibly() | Acquires the lock unless the current thread is interrupted. 獲取鎖,除非當(dāng)前線程被中斷。 |
Condition | newCondition() | Returns a new Condition instance that is bound to this Lock instance. 返回一個(gè)綁定Condition實(shí)例的鎖。 |
boolean | tryLock() | Acquires the lock only if it is free at the time of invocation. 當(dāng)調(diào)用時(shí)鎖為空閑,才能獲取鎖。嘗試獲取鎖。 |
boolean | tryLock(long time, TimeUnit unit) | Acquires the lock if it is free within the given waiting time and the current thread has not been interrupted.在給定時(shí)間內(nèi)處于空閑狀態(tài)且當(dāng)前線程沒(méi)有被中斷時(shí),才能獲取鎖。嘗試獲取鎖+超時(shí)時(shí)間。 |
void | unlock() | Releases the lock. 釋放鎖。 |
按照上面的模板,我們可以自己構(gòu)建一個(gè)鎖工具:
public class MyLock implements Lock { private Helper helper = new Helper(); //按照官方文檔所說(shuō),構(gòu)建內(nèi)部幫助類 private class Helper extends AbstractQueuedSynchronizer { //構(gòu)建嘗試獲取鎖的方法 @Override protected boolean tryAcquire(int arg) { int state = getState(); if (0 == state) { //利用cas的原理修改state if (compareAndSetState(0, arg)) { //設(shè)置當(dāng)前線程擁有資源 setExclusiveOwnerThread(Thread.currentThread()); return true; } }//同一個(gè)線程獲取被鎖住的資源時(shí),直接分配給這個(gè)線程,實(shí)現(xiàn)可重入性 else if(getExclusiveOwnerThread()==Thread.currentThread()){ //刪除這個(gè)else if條件,就會(huì)變?yōu)橐粋€(gè)不可重入鎖 setState(getState()+arg); return true; } return false; } //構(gòu)建嘗試釋放鎖的方法 @Override protected boolean tryRelease(int arg) { //arg是傳遞進(jìn)來(lái)的state的期望值 int state = getState() - arg; //判斷釋放后狀態(tài)是否為0 if (0 == state) { setExclusiveOwnerThread(null); setState(0); return true; } //因?yàn)樾枰薷牡木€程就是當(dāng)前占有鎖的線程,所以此時(shí)直接重置是沒(méi)有線程安全問(wèn)題的,也就是當(dāng)前線程獨(dú)占了資源state setState(state); return false; } public Condition newConditionObject(){ return new ConditionObject(); } } //加鎖方法 @Override public void lock() { helper.acquire(1); //AbstractQueuedSynchronizer原生方法 } //鎖中斷 @Override public void lockInterruptibly() throws InterruptedException { helper.acquireInterruptibly(1); //AbstractQueuedSynchronizer原生方法 } //嘗試獲取鎖 @Override public boolean tryLock() { return helper.tryAcquire(1); //使用自己實(shí)現(xiàn)的tryAcquire()方法 } //嘗試加鎖 @Override public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { return helper.tryAcquireNanos(1,unit.toNanos(time)); //AbstractQueuedSynchronizer原生方法 } //釋放鎖 @Override public void unlock() { helper.tryRelease(1); //使用自己實(shí)現(xiàn)的tryRelease()方法 } //條件 @Override public Condition newCondition() { return helper.newConditionObject(); //AbstractQueuedSynchronizer原生方法 } }
總結(jié)
到此AQS的內(nèi)容告一段落。這篇博客主要講了AQS的設(shè)計(jì)思想,以及操作同步隊(duì)列的方式,同時(shí)完成了一個(gè)簡(jiǎn)單的鎖幫助類,希望能夠幫助大家更好的理解鎖這一個(gè)同步機(jī)制,以及由AQS架構(gòu)為基礎(chǔ)的各種鎖工具的內(nèi)部原理。
到此這篇關(guān)于Java并發(fā)框架中的AQS詳細(xì)解析的文章就介紹到這了,更多相關(guān)Java的AQS內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java Comparator.comparing比較導(dǎo)致空指針異常的解決
這篇文章主要介紹了Java Comparator.comparing比較導(dǎo)致空指針異常的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07springboot整合JavaCV實(shí)現(xiàn)視頻截取第N幀并保存圖片
這篇文章主要為大家詳細(xì)介紹了springboot如何整合JavaCV實(shí)現(xiàn)視頻截取第N幀并保存為圖片,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起了解一下2023-08-08關(guān)于java String中intern的深入講解
這篇文章主要給大家介紹了關(guān)于java String中intern的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用java具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04SpringBoot自定義FailureAnalyzer過(guò)程解析
這篇文章主要介紹了SpringBoot自定義FailureAnalyzer,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11SpringBoot執(zhí)行有返回值的異步任務(wù)問(wèn)題
這篇文章主要介紹了SpringBoot執(zhí)行有返回值的異步任務(wù)問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-07-07關(guān)于如何搭建CAS服務(wù)并將CAS項(xiàng)目導(dǎo)入IDEA
這篇文章主要介紹了關(guān)于如何搭建CAS服務(wù)并將CAS項(xiàng)目導(dǎo)入IDEA的問(wèn)題,文中提供了詳細(xì)的圖文講解,需要的朋友可以參考下,如果有錯(cuò)誤的地方還請(qǐng)指正2023-03-03