Java并發(fā)框架中的AQS詳細(xì)解析
前言
之前說(shuō)鎖的升級(jí)的時(shí)候,說(shuō)到了自旋鎖會(huì)空轉(zhuǎn)幾次嘗試等待獲取資源,其實(shí)這一系列的動(dòng)作是有一個(gè)規(guī)范的這個(gè)規(guī)范叫做同步發(fā)生器AbstractQueuedSynchronizer ,簡(jiǎn)稱(chēng)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)的縮寫(xiě)。
其實(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)稱(chēng)沒(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è)獲取到鎖,以此類(lèi)推。
這里所有的節(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ō):為那些想要依賴(lài)于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. 這句話是用法的概括:其子類(lèi)應(yīng)該被定義為非共有的內(nèi)部幫助器(助手類(lèi)),用于實(shí)現(xiàn)外部類(lèi)的屬性同步。
說(shuō)白了AbstractQueuedSynchronizer就是Java給開(kāi)發(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)吧。我們可以打開(kāi)一個(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()又寫(xiě)了啥。
private Node addWaiter(Node mode) {
//把當(dāng)前線程改造稱(chē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è)鎖類(lèi)
在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)部幫助類(lè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)單的鎖幫助類(lèi),希望能夠幫助大家更好的理解鎖這一個(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-07
springboot整合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-04
SpringBoot自定義FailureAnalyzer過(guò)程解析
這篇文章主要介紹了SpringBoot自定義FailureAnalyzer,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11
SpringBoot執(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

