目錄
1:什么是AQS?
2:AQS都有那些用途?
3:我們?nèi)绾问褂肁QS
4:AQS的實現(xiàn)原理
5:對AQS的設(shè)計與實現(xiàn)的一些思考
1:什么是AQS
? 隨著計算機的算力越來越強大,各種各樣的并行編程模型也隨即踴躍而來,但當(dāng)我們要在并行計算中使用共享資源的時候,就需要利用一種手段控制對共享資源的訪問和修改來保證我們程序的正確的運行。而Java中除了在語言級別實現(xiàn)的synchronized鎖之外,還有另一種對共享資源訪問控制的實現(xiàn),而這些實現(xiàn)都依賴于一個叫做抽象隊列同步器(AbstractQueuedSynchronizer)的模板框架類,簡稱AQS。所以我們想要更深刻的理解Java中對共享資源的訪問控制,就不可避免的要對AQS深入的了解和探討。
? 我們首先看一下官方對于AQS的描述:提供一個依賴先進先出(FIFO)等待隊列的掛起鎖和相關(guān)同步器(信號量、事件等)框架。此類被設(shè)計為大多數(shù)類型的同步器的基類,這些同步器依賴于單個原子int值來表示狀態(tài)。子類必須定義更改該狀態(tài)的受保護方法,以及定義該狀態(tài)在獲取或釋放該對象方面的含義??紤]到這些,類中的其他方法實現(xiàn)排隊和掛起機制。子類可以維護狀態(tài)字段,但只有使用方法getState、setState和compareAndSetState方法才可以更改狀態(tài)。
2:AQS有哪些用途

? AQS的用途有很多,幾乎Java中所有的共享資源控制的實現(xiàn)都離不開它,比如我們常用的鎖ReentrantLock、是基于AQS實現(xiàn)了一套可重入的公平和非公平互斥鎖的實現(xiàn)。上圖中我列舉了我們常用的一些對于共享資源訪問控制的一些工具,也都是基于AQS實現(xiàn)的。
3:如何使用AQS
? 我們要是用AQS實現(xiàn)我們自己的鎖,都離不開AQS中一個叫做int類型state的變量,而這個變量的具體意義是由使用者自已定義的,比如我們要基于AQS實現(xiàn)一個不可重入的互斥鎖,我們可以定義state為1代表有線程已經(jīng)獲取了鎖,state為0為空閑狀態(tài)。
下面是AQS文檔中的一段使用AQS自定義互斥鎖的一段示例代碼,我把它放到此處,方便大家查閱。
class Mutex implements Lock, java.io.Serializable {
// Our internal helper class
private static class Sync extends AbstractQueuedSynchronizer {
//Reports whether in locked state
protected boolean isHeldExclusively() {
return getState() == 1;
}
// Acquires the lock if state is zero
public boolean tryAcquire(int acquires) {
assert acquires == 1; // Otherwise unused
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
// Releases the lock by setting state to zero
protected boolean tryRelease(int releases) {
assert releases == 1; // Otherwise unused
if (getState() == 0) throw new IllegalMonitorStateException();
setExclusiveOwnerThread(null);
setState(0);
return true;
}
// Provides a Condition
Condition newCondition() {
return new ConditionObject();
}
// Deserializes properly
private void readObject(ObjectInputStream s)
throws IOException, ClassNotFoundException {
s.defaultReadObject();
setState(0); // reset to unlocked state
}
}
// The sync object does all the hard work. We just forward to it.
private final Sync sync = new Sync();
public void lock() {
sync.acquire(1);
}
public boolean tryLock() {
return sync.tryAcquire(1);
}
public void unlock() {
sync.release(1);
}
public Condition newCondition() {
return sync.newCondition();
}
public boolean isLocked() {
return sync.isHeldExclusively();
}
public boolean hasQueuedThreads() {
return sync.hasQueuedThreads();
}
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
}
4:AQS的實現(xiàn)原理
? AQS實現(xiàn)的核心思想是,如果被請求的共享資源空閑,那么就將當(dāng)前請求資源的線程設(shè)置為有效的工作線程,將共享資源設(shè)置為鎖定狀態(tài);如果共享資源被占用,就需要將此線程放入一個叫做CLH(三個人名Craig、Landin and Hagersten)的等待隊列中,然后通過掛起和喚醒機制來保證鎖的分配。而將資源設(shè)置為鎖定狀態(tài)主要是通過說到的一個叫做int類型的state的變量來控制的,隊列的FIFO操作則是利用CLH隊列來實現(xiàn)。
等待隊列是“CLH”(Craig、Landin和Hagersten)鎖隊列的變體。我們使用它們來作為同步器,在其節(jié)點中存儲有關(guān)線程的一些控制信息。每個節(jié)點中的status字段跟蹤線程是否應(yīng)該掛起。當(dāng)節(jié)點的前驅(qū)節(jié)點釋放時,節(jié)點會發(fā)出信號。隊列的每個節(jié)點在其他方面都充當(dāng)一個特定的通知樣式監(jiān)視器,其中包含一個等待線程。要進入CLH鎖的隊列,可以將其原子性地拼接在隊列的尾部。要退出隊列,只需將其放在隊列頭部,也就是將此節(jié)點設(shè)置為head字段,原理圖如下。

4.1:AQS的數(shù)據(jù)結(jié)構(gòu)
首先我們先看一下AQS中最基本的數(shù)據(jù)結(jié)構(gòu),也就是CLH隊列中的節(jié)點,是一個名為Node的靜態(tài)內(nèi)部類
static final class Node {
/** 標(biāo)記此節(jié)點是一個以共享模式等待的鎖 */
static final Node SHARED = new Node();
/** 標(biāo)記此節(jié)點是一個以互斥模式等待的鎖 */
static final Node EXCLUSIVE = null;
/** 表示線程獲取鎖的線程已經(jīng)取消了*/
static final int CANCELLED = 1;
/** 原文注釋:waitStatus value to indicate successor's thread needs unparking
表示此線程的后繼線程需要通過unpark()方法喚醒。
我的理解是此線程的后繼節(jié)點需要被喚醒,
但當(dāng)前節(jié)點必須是釋放鎖或者取消獲取鎖,后繼線程等待被喚醒獲取鎖,后續(xù)可以通過源碼解釋。
*/
static final int SIGNAL = -1;
/** 表示節(jié)點在等待隊列中,節(jié)點線程等待喚醒,在使用Conditional的時候會有此狀態(tài) */
static final int CONDITION = -2;
/**
* 當(dāng)前線程處在SHARED情況下,該字段才會使用
*/
static final int PROPAGATE = -3;
/**
這些值以數(shù)字形式排列,以簡化使用。非負值表示節(jié)點不需要信號。因此,大多數(shù)代碼不需要檢查特定的值,僅用檢查符號就可以 了。對于正常同步節(jié)點,該字段初始化為0,并且條件節(jié)點的CONDITION。使用CAS進行修改。
*/
volatile int waitStatus;
/**
前驅(qū)節(jié)點
*/
volatile Node prev;
/**
后繼節(jié)點
*/
volatile Node next;
/**
* 此節(jié)點的線程
*/
volatile Thread thread;
/**
指向下一個處于CONDITION狀態(tài)的節(jié)點,使用Conditional模式才會用到此節(jié)點,
篇幅原因,本片不講述Condition Queue隊列,故對此字段不多作介紹。
*/
Node nextWaiter;
/**
* 是否是共享模式
*/
final boolean isShared() {
return nextWaiter == SHARED;
}
/**
* 返回當(dāng)前節(jié)點的前驅(qū)節(jié)點,前驅(qū)節(jié)點為null,則拋出NPE
*/
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
Node() { // Used to establish initial head or SHARED marker
}
Node(Thread thread, Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) { // Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
}
AQS中其他重要字段
/**
* 隊列的頭節(jié)點(虛節(jié)點,不存儲數(shù)據(jù)),懶初始化。除了初始化之外,僅能通過setHead方法修改。
* 注:假如頭節(jié)點存在,一定會保證waitStatus變量的值不是CANCELLED
*/
private transient volatile Node head;
/**
* 隊列的尾節(jié)點(虛節(jié)點,不存儲數(shù)據(jù)),懶初始化,僅僅可以通過enq方法初始化
*/
private transient volatile Node tail;
/**
* 同步狀態(tài)
*/
private volatile int state;
由靜態(tài)內(nèi)部類Node中的prev和next字段我們可以知道CLH變體隊列是一個由Node組成的雙向隊列,由字段head和tail可以知道AQS中保存了頭節(jié)點和尾節(jié)點。state字段則是用來作為同步的重要字段,AQS中提供了三個final類型的方法來訪問該字段。
protected final int getState() {
return state;
}
protected final void setState(int newState) {
state = newState;
}
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
4.2:模板方法
/**
嘗試以獨占模式獲取鎖,此方法此方法應(yīng)查詢對象的狀態(tài)是否允許以獨占模式獲取該對象,若允許的話則可以獲取它。
此方法總是由執(zhí)行acquire方法的線程調(diào)用。如果此方法返回false且線程尚未排隊,則acquire方法可以對線程進行排隊,直到某 個其他線程發(fā)出釋放信號,則該線程停止掛起。
*/
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
/**
嘗試以獨占模型釋放鎖
*/
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
/**
嘗試以共享模式獲取鎖,此方法此方法應(yīng)查詢對象的狀態(tài)是否允許以獨占模式獲取該對象,若允許的話則可以獲取它。
此方法總是由執(zhí)行acquire方法的線程調(diào)用。如果此方法返回false且線程尚未排隊,則acquire方法可以對線程進行排隊,直到某 個其他線程發(fā)出釋放信號,則該線程停止掛起。
*/
protected int tryAcquireShared(int arg) {
throw new UnsupportedOperationException();
}
/**
嘗試以共享模式釋放鎖
*/
protected boolean tryReleaseShared(int arg) {
throw new UnsupportedOperationException();
}
/**
返回是否以獨占模式持有鎖
*/
protected boolean isHeldExclusively() {
throw new UnsupportedOperationException();
}
思考:AQS作為一個模板框架類,為什么 tryLock和tryRelease等方法,那么為什么這些方法不定義成abstract方法,而要定義成普通方法,且在方法中拋出異常呢?我的理解是AQS作為一個模板框架提供了加鎖和釋放鎖的通用邏輯,但是又不僅僅是提供了獨占鎖或共享鎖的邏輯,而是作為一個底層的通用執(zhí)行模板類,如何定義成抽象的模板方法,那么所有的子類都要實現(xiàn)所有的模板方法,但是有些子類僅僅需要獨占鎖,比如ReentrantLock,那么就會要多先實現(xiàn)共享鎖的邏輯(即使是空方法也要實現(xiàn)),所以我猜想是為了子類不必要實現(xiàn)多余的方法,所以才定義成普通的方法并在方法內(nèi)部拋出異常。
4.3:獲取鎖源碼及流程分析
由于篇幅原因,本文將以獨占且忽略中斷的模式的方法未入口分析,首先看一下獲取鎖的方法,獲取鎖的總體流程圖如下:
獲取鎖的入口方法
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
//acquireQueued方法會返回線程在掛起過程中是否被中斷,然后返回線程中斷的狀態(tài)
selfInterrupt();
}
可以看到獲取鎖的方法是一個由final修飾的不可被子類重寫的方法,首先調(diào)用了上面的模板方法(必須由子類重寫獲取邏輯)獲取鎖
如果獲取成功則獲取鎖流程執(zhí)行結(jié)束。否則執(zhí)行addWaiter方法執(zhí)行入隊邏輯。
線程入隊方法
private Node addWaiter(Node mode) {
//mode線程獲取鎖的模式(獨占或者共享)
Node node = new Node(Thread.currentThread(), mode);
//嘗試快速入隊,失敗則執(zhí)行完整入隊邏輯
Node pred = tail;
if (pred != null) {
node.prev = pred;
//如果尾節(jié)點不為null,則以原子方式把當(dāng)前節(jié)點設(shè)置為尾節(jié)點并返回
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//如果尾節(jié)點不存(說明頭節(jié)點和尾節(jié)點未初始化)在或者由于競爭導(dǎo)致一次設(shè)置尾節(jié)點失敗,
//則執(zhí)行完整入隊邏輯(會進行頭節(jié)點和尾節(jié)點初始化的工作)
enq(node);
return node;
}
addWaiter方法會先進行快速入隊操作,如果快速入隊失?。ㄓ捎诟偁幓蛘哳^、尾節(jié)點未初始化),則進行完整入隊操作(如果頭、尾節(jié)點未初始化的話會先進行初始化操作)
完整入隊邏輯
private Node enq(final Node node) {
//自旋把當(dāng)前節(jié)點鏈接到尾節(jié)點之后
for (;;) {
Node t = tail;
if (t == null) {
//尾節(jié)為空,說明隊列為空,則需要進行頭節(jié)點和尾節(jié)點的初始化
//這里直接new Node(),一個虛節(jié)點作為頭節(jié)點,然后將頭節(jié)點和尾節(jié)點相同
//這里說明頭節(jié)點和尾節(jié)點不存儲數(shù)據(jù)
if (compareAndSetHead(new Node()))
tail = head;
} else {
//尾節(jié)點不為空,使用cas把當(dāng)前節(jié)點設(shè)置為尾節(jié)點
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
enq方法會利用自旋先檢查頭節(jié)點和尾節(jié)點是否初始化,如果未初始化的話則先進行初始化。初始化完成之后以原子的方式插入node到隊尾并且插入成功之后返回此節(jié)點。
掛起線程并等待獲取鎖
final boolean acquireQueued(final Node node, int arg) {
//是否失敗,此線程被中斷可能失敗
boolean failed = true;
try {
//線程是否被中斷
boolean interrupted = false;
//自旋一直獲取鎖
for (;;) {
//獲取當(dāng)前節(jié)點的前驅(qū)節(jié)點
final Node p = node.predecessor();
//如果當(dāng)前節(jié)點的前驅(qū)節(jié)點是頭節(jié)點(因為頭節(jié)點是虛節(jié)點,所以當(dāng)前節(jié)點可以獲取鎖),
//且當(dāng)前節(jié)點獲取所成功
if (p == head && tryAcquire(arg)) {
//設(shè)置node為頭節(jié)點,因為當(dāng)前節(jié)點已經(jīng)獲取鎖成功了,當(dāng)前節(jié)點需要作為頭節(jié)點
setHead(node);
p.next = null; // help GC
//設(shè)置失敗標(biāo)志為false
failed = false;
//返回中斷狀態(tài)
return interrupted;
}
//shouldParkAfterFailedAcquire方法檢查并更新獲取失敗的節(jié)點的狀態(tài),如果線程應(yīng)該掛起則返回true
//parkAndCheckInterrupt則掛起線程并返回是否中斷
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
//失敗,則取消獲取鎖
if (failed)
cancelAcquire(node);
}
}
? 通過上面流程分析可知,獲取鎖失敗,會調(diào)用addWaiter方法把當(dāng)前節(jié)點放到隊尾,那么線程入隊之后什么時候掛起線程,什么時候出隊,我們一點一點分析acquireQueued方法這些問題就會逐漸顯露出來。
? 首先該方法會一直自旋獲取鎖(中間可能會被掛起,防止無效自旋),判斷當(dāng)前節(jié)點的前驅(qū)節(jié)點是否是頭節(jié)點來判斷當(dāng)前是否有獲取鎖的資格,如果有的話則設(shè)置當(dāng)前節(jié)點為頭節(jié)點并返回中斷狀態(tài)。否則調(diào)用shouldParkAfterFailedAcquire判斷獲取鎖失敗后是否可以掛起,可以的話則調(diào)用parkAndCheckInterrupt進行線程掛起操作。
設(shè)置頭節(jié)點
private void setHead(Node node) {
head = node;
node.thread = null;
node.prev = null;
}
注:setHead方法是把當(dāng)前節(jié)點置為虛節(jié)點,但并沒有修改waitStatus,因為其他地方要用到。
檢查并更新獲取失敗的節(jié)點的狀態(tài)
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
* node的狀態(tài)已經(jīng)是SIGNAL可以安全的掛起,直接返回true
*/
return true;
if (ws > 0) {
/*
* 由之前的waitStatus變量枚舉值可知,waitStatus大于0為取消狀態(tài),直接跳過此節(jié)點
*/
do {
//重新鏈接prev指針,至于為什么沒有操作next指針后面會通過代碼解釋
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* 原子方式設(shè)置waitStatus的值為SIGNAL
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
掛起并檢查線程的中斷狀態(tài)
private final boolean parkAndCheckInterrupt() {
//使用LockSupport掛起此線程
LockSupport.park(this);
//返回并清除中斷狀態(tài)
return Thread.interrupted();
}
取消獲取鎖
private void cancelAcquire(Node node) {
// 忽略不存在的節(jié)點
if (node == null)
return;
//設(shè)置當(dāng)前節(jié)點不持有線程
node.thread = null;
// 跳過取消的前驅(qū)節(jié)點
Node pred = node.prev;
//waitStatus>0未已取消的節(jié)點
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
// 未取消的節(jié)點的后繼節(jié)點
Node predNext = pred.next;
//設(shè)置狀態(tài)未取消狀態(tài)
node.waitStatus = Node.CANCELLED;
// 如果node為尾節(jié)點,設(shè)置pred為尾節(jié)點,然后設(shè)置尾節(jié)點的下一個節(jié)點為null
if (node == tail && compareAndSetTail(node, pred)) {
compareAndSetNext(pred, predNext, null);
} else {
int ws;
// 如果當(dāng)前節(jié)點不是head的后繼節(jié)點,
//1:判斷當(dāng)前節(jié)點前驅(qū)節(jié)點的是否為SIGNAL,
//2:如果不是,則把前驅(qū)節(jié)點設(shè)置為SINGAL看是否成功
// 如果1和2中有一個為true,再判斷當(dāng)前節(jié)點的線程是否為null
// 如果上述條件都滿足,把當(dāng)前節(jié)點的前驅(qū)節(jié)點的后繼指針指向當(dāng)前節(jié)點的后繼節(jié)點
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
Node next = node.next;
if (next != null && next.waitStatus <= 0)
//為什么沒有自旋,如果此處設(shè)置失敗,下次仍然會丟掉predNext到next中間節(jié)點,所以不會出現(xiàn)問題
compareAndSetNext(pred, predNext, next);
} else {
//當(dāng)前節(jié)點是頭節(jié)點的后繼節(jié)點或者上述條件都不滿足
unparkSuccessor(node);
}
//為什么此處能help GC,不得不多說Doug Lea大神心思之縝密,考慮之周全。
//解釋參考http://www.codebaoku.com/question/question-sd-1010000043795300.html
node.next = node; // help GC
}
}
當(dāng)node==tail時,節(jié)點變化情況如下圖

當(dāng)pred==head時,節(jié)點的變化情況如下圖

當(dāng)pred!=head時,且上述條件滿足時節(jié)點的變化情況如下圖

通過上面的流程,我們對于取消獲取鎖的cancelAcquire方法對節(jié)點操作狀態(tài)的產(chǎn)生和變化已經(jīng)有了一定的了解,但是為什么所有的變化都是對next指針進行了操作,而沒有對Prev指針進行操作呢?帶著這個問題我們回顧一下shouldParkAfterFailedAcquire方法。
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
原因:執(zhí)行cancelAcquire的時候,當(dāng)前節(jié)點的前置節(jié)點可能已經(jīng)從隊列中出去了(已經(jīng)執(zhí)行過Try代碼塊中的shouldParkAfterFailedAcquire方法了),如果此時修改Prev指針,有可能會導(dǎo)致Prev指向另一個已經(jīng)移除隊列的Node,因此這塊變化Prev指針不安全。 shouldParkAfterFailedAcquire方法中,會執(zhí)行下面的代碼,其實就是在處理Prev指針。shouldParkAfterFailedAcquire是獲取鎖失敗的情況下才會執(zhí)行,進入該方法后,說明共享資源已被獲取,當(dāng)前節(jié)點之前的節(jié)點都不會出現(xiàn)變化,因此這個時候變更Prev指針比較安全。
喚醒后繼節(jié)點
private void unparkSuccessor(Node node) {
/*
* node一般為head節(jié)點,如果waitStatus為負,則嘗試清除信號,設(shè)置為0
*/
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
* 正常情況下我們是要喚醒頭節(jié)點的后繼節(jié)點,但是如果后繼節(jié)點為空或者已被取消,
* 則需要從尾節(jié)點開始,找到離頭節(jié)點最近的未被取消的后繼節(jié)點。
*/
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
// 如果當(dāng)前節(jié)點的下個節(jié)點不為空,而且狀態(tài)小于等于0,就把當(dāng)前節(jié)點喚醒
if (s != null)
LockSupport.unpark(s.thread);
}
為什么需要從后往前找呢?從美團技術(shù)團隊中的一片文章中(https://tech.meituan.com/2019/12/05/aqs-theory-and-apply.html)找到了答案,我把大佬的解釋放到下面,供大家參考!
4.4:釋放鎖源碼及流程分析
釋放鎖流程圖如下:
public final boolean release(int arg) {
//嘗試釋放鎖成功
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
//嘗試釋放鎖失敗
return false;
}
釋放鎖的流程就比較簡單了,先嘗試釋放鎖,如果釋放鎖成功,(如果頭節(jié)點不為null且頭節(jié)點的狀態(tài)不等于0則釋放頭節(jié)點的后繼節(jié)點)返回true,否則返回false。
5:對AQS的設(shè)計與實現(xiàn)的一些思考
? 1:設(shè)計方面,AQS作為底層一個通用的模板框架類,就要考慮到一些易用性和擴展性,比如作者模板方法使用的拋出異常,而不是作為抽象方法強制使用者實現(xiàn)所有的模板方法,而是使用者可以自由選擇要使用的方法和特性選擇性實現(xiàn)模板方法,當(dāng)然也犧牲掉了一些其他東西,比如設(shè)計原則的最小職責(zé)性。這也就體現(xiàn)了一些折衷的思想,任何方案都不是完美的,我們只有權(quán)衡利弊之后達到一個相對完美的方案。
? 2:實現(xiàn)方面,AQS的實現(xiàn)不得不令人驚嘆,每一次讀都會想到書讀百遍,其意自現(xiàn)這句名言,每次讀有不一樣的收獲,看似一行不經(jīng)意的代碼,體現(xiàn)了作者對每一行代碼細致又獨到的思考。在讀AQS代碼的時候參考下面的鏈接,看大牛對AQS的的理解時見解,不僅加深了我對AQS核心思想的理解,也讓我從另一方面看到了AQS的優(yōu)秀之處(由于個人水平有限,理解不到位或者錯誤還請各位道友不吝賜教)。路漫漫其修遠兮,吾將上下而求索。
參考:
https://tech.meituan.com/2019/12/05/aqs-theory-and-apply.html文章來源:http://www.zghlxwxcb.cn/news/detail-534573.html
//help GC 相關(guān)的解釋
http://www.codebaoku.com/question/question-sd-1010000043795300.html文章來源地址http://www.zghlxwxcb.cn/news/detail-534573.html
到了這里,關(guān)于Java中AQS的原理與實現(xiàn)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!