前言
AQS是AbstractQueuedSynchronizer
類的簡稱,雖然我們不會直接使用這個類,但是這個類是Java很多併發工具的底層實現。本文主要從源碼的角度,全方位的解析AQS類。
底層實現
首先看下哪些併發工具類是使用AQS實現的,使用IDEA就可以看到
可以看到,CountDownLatch
、Semaphore
、ReentrantLock
等等常見的工具類都是由AQS
來實現的。所以不管是面試也好,還是自己研究底層實現也好,AQS
類都是必須要重點關注的。
前置閱讀
對上述工具類不太瞭解的同學建議先看下以下幾篇
AQS
首先從AQS
類的定義開始,逐步深入瞭解。AQS
類的定義如下
/**
* 可以看到AbstractQueuedSynchronizer是一個抽象類
* 實現了Serializable 接口
* @since 1.5
* @author Doug Lea
*/
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
/**
* The synchronization state.
* state變量表示鎖的狀態
* 0 表示未鎖定
* 大於0表示已鎖定
* 需要注意的是,這個值可以用來實現鎖的【可重入性】,例如 state=3 就表示鎖被同一個線程獲取了3次,想要完全解鎖,必須要對應的解鎖3次
* 同時這個變量還是用volatile關鍵字修飾的,保證可見性
*/
private volatile int state;
/**
* 等待隊列的頭節點,只能通過setHead方法修改
* 如果head存在,能保證waitStatus狀態不爲CANCELLED
*/
private transient volatile Node head;
/**
* 等待隊列的尾結點,只能通過enq方法來添加新的等待節點
*/
private transient volatile Node tail;
}
AbstractQueuedSynchronizer
從名字上就可看出本質是一個隊列(Queue
),其內部維護着FIFO的雙向隊列,也就是CLH隊列。
CLH (Craig, Landin, and Hagersten) lock queue
這個隊列中的每一個元素都是一個Node
,所以接下來了解一下其內部類Node
,內部類Node
的定義如下
static final class Node {
// 節點正在共享模式下等待的標記
static final Node SHARED = new Node();
// 節點正在以獨佔模式等待的標記
static final Node EXCLUSIVE = null;
// waitStatus變量的可選值,因爲超時或者或者被中斷,節點會被設置成取消狀態。被取消的節點不會參與鎖競爭,狀態也不會再改變
static final int CANCELLED = 1;
// waitStatus變量的可選值,表示後繼節點處於等待狀態,如果當前節點釋放了鎖或者被取消,會通知後繼節點去運行
static final int SIGNAL = -1;
// waitStatus變量的可選值,表示節點處於condition隊列中,正在等待被喚醒
static final int CONDITION = -2;
// waitStatus變量的可選值,下一次acquireShared應該無條件傳播
static final int PROPAGATE = -3;
// 節點的等待狀態
volatile int waitStatus;
// 前驅節點
volatile Node prev;
// 後繼節點
volatile Node next;
// 獲取同步狀態的線程
volatile Thread thread;
// 下一個condition隊列等待節點
Node nextWaiter;
// 是否是共享模式
final boolean isShared() {
return nextWaiter == SHARED;
}
// 返回前驅節點或者拋出異常
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的基本結構
核心方法
我們都知道CountDownLatch
、CyclicBarrier
、Semaphore
、ReentrantLock
這些工具類中,有的只支持獨佔,如ReentrantLock#lock()
,有的支持共享,多個線程同時執行,如Semaphore
。並且,從前文Node
類的定義也可以看到
// 節點正在共享模式下等待的標記
static final Node SHARED = new Node();
// 節點正在以獨佔模式等待的標記
static final Node EXCLUSIVE = null;
AQS
實現了兩套加鎖解鎖的方式,那就是獨佔式和共享式。我們先看下獨佔式的實現,獨佔式的實現,就從ReentrantLock#lock()
方法開始。
ReentrantLock#lock
該方法定義如下
public void lock() {
sync.lock();
}
其中sync
是AbstractQueuedSynchronizer
的實現,我們知道,ReentrantLock
支持公平鎖和非公平鎖,其實現類分別是FairSync
和NonfairSync
,我們看看公平鎖和非公平鎖分別是怎麼實現的
// FairSync 公平鎖的實現
final void lock() {
acquire(1);
}
// NonfairSync 非公平鎖的實現
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
可以看到,非公平鎖的實現僅僅是多了一個步驟:通過CAS
的方式(compareAndSetState
)嘗試改變state
的狀態,修改成功後設置當前線程以獨佔的方式獲取了鎖,修改失敗執行的邏輯和公平鎖一樣。
這就是公平鎖和非公平鎖的本質區別
從這段代碼中可以看到,獨佔鎖加鎖的核心邏輯就是acquire
方法,接下來就看看這個方法
acquire
該方法定義如下
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
該方法主要調用tryAcquire
方法嘗試獲取鎖,成功返回true
,失敗就將線程封裝成Node
對象,放入隊列。
tryAcquire
tryAcquire
方法在AQS
中並沒有直接實現,而是採用模板方法的設計模式,交給子類去實現。我們來看公平鎖的實現。
protected final boolean tryAcquire(int acquires) {
// 當前線程
final Thread current = Thread.currentThread();
// 獲取state狀態,0表示未鎖定,大於1表示重入
int c = getState();
if (c == 0) {
// 表示沒有線程獲取鎖
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
// 沒有比當前線程等待更久的線程了,通過CAS的方式修改state
// 成功之後,設置當前擁有獨佔訪問權的線程
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
// 獨佔訪問權的線程就是當前線程,重入
// 此處就是【可重入性】的實現
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
// 直接修改state
setState(nextc);
return true;
}
return false;
}
可以看到該方法就是以獨佔的方式獲取鎖,獲取成功後返回true
。從這個方法可以看出state
變量是實現可重入性的關鍵。
非公平鎖的實現方式大同小異,感興趣的同學可以自行閱讀源碼。
acquire
方法除了調用tryAcquire
,還調用了acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
,這裏分爲兩步,先看下addWaiter
方法。
addWaiter
該方法用於把當前線程封裝成一個Node
節點,並加入隊列。方法定義如下
/**
* Creates and enqueues node for current thread and given mode.
* 爲當前線程和給定模式創建並排隊節點,給的的模式分爲:
* 1、Node.EXCLUSIVE:獨佔模式
* 2、Node.SHARED:共享模式
*
* @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
*/
private Node addWaiter(Node mode) {
// 創建Node節點
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
// 嘗試快速添加尾結點,失敗就執行enq方法
Node pred = tail;
if (pred != null) {
node.prev = pred;
// CAS的方式設置尾結點
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
// 快速添加失敗,執行該方法
enq(node);
return node;
}
enq
方法定義如下
/**
* Inserts node into queue, initializing if necessary. See picture above.
* 將節點插入隊列,必要時進行初始化
*
* @param node the node to insert
* @return node's predecessor
*/
private Node enq(final Node node) {
for (;;) {
// 自旋
Node t = tail;
if (t == null) { // Must initialize
// 尾結點爲空,隊列還沒有進行初始化
// 設置頭節點
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
// CAS的方式設置尾結點,失敗就進入下次循環
// 也就是【自旋 + CAS】的方式保證設置成功
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
可以看到該方法就是用來往隊列尾部插入一個新的節點,通過自旋 + CAS的方式保證線程安全和插入成功。
需要注意的是,該方法返回的Node節點不是新插入的節點,而是新插入節點的前驅節點。
acquireQueued
該方法定義如下
/**
* Acquires in exclusive uninterruptible mode for thread already in
* queue. Used by condition wait methods as well as acquire.
*
*/
final boolean acquireQueued(final Node node, int arg) {
// 操作是否成功
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
// 自旋
// 獲取當前節點的前驅節點
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
// 前驅節點是頭節點,並且已經獲取了鎖(tryAcquire方法在前文中詳細講解過)
// 就把當前節點設置成頭節點(因爲前驅節點已經獲取了鎖,所以前驅節點不用再留在隊列)
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
// 如果前驅節點不是頭節點或者沒有獲取鎖
// shouldParkAfterFailedAcquire方法用於判斷當前線程是否需要被阻塞
// parkAndCheckInterrupt方法用於阻塞線程並且檢測線程是否被中斷
// 沒搶到鎖的線程需要被阻塞,避免一直去爭搶鎖,浪費CPU資源
interrupted = true;
}
} finally {
if (failed)
// 自旋異常退出,取消正在進行鎖爭搶
cancelAcquire(node);
}
}
shouldParkAfterFailedAcquire
shouldParkAfterFailedAcquire
方法定義如下,用於判斷當前線程是否需要被阻塞
/**
* Checks and updates status for a node that failed to acquire.
* Returns true if thread should block. This is the main signal
* control in all acquire loops. Requires that pred == node.prev.
*
* @param pred node's predecessor holding status
* @param node the node
* @return {@code true} if thread should block
*/
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
// 獲取前驅節點的等待狀態
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
* SIGNAL表示後繼節點處於等待狀態,如果當前節點釋放了鎖或者被取消,會通知後繼節點去運行
* 所以作爲後繼節點,node直接返回true,表示需要被阻塞
*/
return true;
if (ws > 0) {
/*
* 前驅節點被取消了,需要從隊列中移除,並且循環找到下一個不是取消狀態的節點
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* 通過CAS將前驅節點的status設置成SIGNAL
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
parkAndCheckInterrupt
parkAndCheckInterrupt
方法定義如下,用於阻塞線程並且檢測線程是否被中斷
private final boolean parkAndCheckInterrupt() {
// 阻塞當前線程
LockSupport.park(this);
// 檢測當前線程是否被中斷(該方法會清除中斷標識位)
return Thread.interrupted();
}
至此,獨佔鎖的整個加鎖過程就已經完成。再來回顧下整個流程
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
首先執行tryAcquire
方法用於嘗試獲取鎖,成功後就直接返回,失敗後就通過addWaiter
方法把當前線程封裝成一個Node
,加到隊列的尾部,再通過acquireQueued
方法嘗試獲取同步鎖,成功獲取鎖的線程的Node
節點會被移出隊列。
如果以上條件都滿足,會執行selfInterrupt
方法中斷當前線程。
看完了獨佔鎖的加鎖,再來看看獨佔鎖的解鎖。同樣從ReentrantLock
入手
ReentrantLock#unlock
方法定義如下
public void unlock() {
sync.release(1);
}
我們已經知道了sync
是AQS的實現,所以直接查看AQS中的release
方法
/**
* Releases in exclusive mode. Implemented by unblocking one or
* more threads if {@link #tryRelease} returns true.
* This method can be used to implement method {@link Lock#unlock}.
*
* @param arg the release argument. This value is conveyed to
* {@link #tryRelease} but is otherwise uninterpreted and
* can represent anything you like.
* @return the value returned from {@link #tryRelease}
*/
public final boolean release(int arg) {
if (tryRelease(arg)) {
// 嘗試釋放鎖
Node h = head;
if (h != null && h.waitStatus != 0)
// 頭節點已經釋放,喚醒後繼節點
unparkSuccessor(h);
return true;
}
return false;
}
相信大家已經猜到了,和加鎖時一樣,這裏的tryRelease
方法同樣使用了模板方法的設計模式,其真正的邏輯由子類實現
tryRelease
方法定義如下
protected final boolean tryRelease(int releases) {
// 計算剩餘的重入次數
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
// 是否完全的釋放了鎖(針對可重入性)
boolean free = false;
if (c == 0) {
// 表示完全釋放了鎖
free = true;
// 設置獨佔鎖的持有者爲null
setExclusiveOwnerThread(null);
}
// 設置AQS的state
setState(c);
return free;
}
unparkSuccessor
unparkSuccessor
方法用於喚醒後繼節點,其定義如下
/**
* Wakes up node's successor, if one exists.
*
* @param node the node
*/
private void unparkSuccessor(Node node) {
// 獲取當前節點的狀態
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
if (s == null || s.waitStatus > 0) {
// 當前節點的後繼節點爲null,或者被取消了
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
// 從尾結點查找狀態不爲取消的可用節點
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
// 喚醒後繼節點
LockSupport.unpark(s.thread);
}
前文說過AQS實現了兩套同步邏輯,也就是獨佔式和共享式。看完了獨佔式鎖的實現,再來看一下共享式。這裏以Semaphore
爲例。
Semaphore#acquire
該方法是作用是請求一個許可,如果暫時沒有可用的許可,則被阻塞,等待將來的某個時間被喚醒。因爲Semaphore
可以允許多個線程同時執行,所以可以看成是共享鎖的實現。該方法定義如下
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
sync
是AQS的實現,可以看到acquire
方法底層調用的是acquireSharedInterruptibly
方法。
在JDK中,與鎖相關的方法,Interruptibly
表示可中斷,也就是可中斷鎖。可中斷鎖的意思是線程在等待獲取鎖的過程中可以被中斷,換言之,線程在等待鎖的過程中可以響應中斷。
接下來看看acquireSharedInterruptibly
方法的實現
acquireSharedInterruptibly
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
// 檢測線程的中斷中斷狀態,如果已經被中斷了,就響應中斷
// 該方法會清除線程的中斷標識位
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
tryAcquireShared
tryAcquireShared
方法,相信大家已經能看出來,這裏使用了模板方法模式,具體實現由子類去實現。Semaphore
也實現了公平模式和非公平模式。公平的方式和非公平的方式實現邏輯大同小異。所以具體看下公平模式下的實現方式
protected int tryAcquireShared(int acquires) {
for (;;) {
// 自旋
if (hasQueuedPredecessors())
// 如果有線程排在自己的前面(公平鎖排隊),直接返回
return -1;
// 獲取同步狀態的值
int available = getState();
// 可用的(許可)減去申請的,等於剩餘的
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
// 如果剩餘的小於0,或者設置狀態成功,就返回,如果設置失敗,則進入下一次循環
// 如果剩餘小於0,返回負數,表示失敗
// 如果設置狀態成功,表示申請許可成功,返回正數
return remaining;
}
}
此處還是自旋 + CAS的方式保證線程安全和設置成功。
doAcquireSharedInterruptibly
doAcquireSharedInterruptibly
方法定義如下
/**
* Acquires in shared interruptible mode.
* 在共享可中斷模式下請求(許可)
*/
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
// 爲當前線程和給定模式創建節點並插入隊列尾部,addWaiter方法前文講解過
final Node node = addWaiter(Node.SHARED);
// 操作是否失敗
boolean failed = true;
try {
for (;;) {
// 自旋
// 獲取當前節點的前驅節點
final Node p = node.predecessor();
if (p == head) {
// 如果前驅節點是頭節點,以共享的方式請求獲取鎖,tryAcquireShared方法前文講解過
int r = tryAcquireShared(arg);
if (r >= 0) {
// 成功獲取鎖,設置頭節點和共享模式傳播
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
// 如果前驅節點不是頭節點或者沒有獲取鎖
// shouldParkAfterFailedAcquire方法用於判斷當前線程是否需要被阻塞,該方法前文講解過
// parkAndCheckInterrupt方法用於阻塞線程並且檢測線程是否被中斷,該方法前文講解過
// 沒搶到鎖的線程需要被阻塞,避免一直去爭搶鎖,浪費CPU資源
throw new InterruptedException();
}
} finally {
if (failed)
// 自旋異常退出,取消正在進行鎖爭搶
cancelAcquire(node);
}
}
加鎖的邏輯已經完成,再來看看解鎖的邏輯。
Semaphore#release
release
用於釋放許可,其方法定義如下
public void release() {
sync.releaseShared(1);
}
releaseShared
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
tryReleaseShared
protected final boolean tryReleaseShared(int releases) {
for (;;) {
// 自旋
// 獲取同步狀態的值
int current = getState();
// 可用的(許可)加上釋放的,等於剩餘的
int next = current + releases;
if (next < current) // overflow
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next))
// CAS的方式設置同步狀態
return true;
}
}
可以看到此處依舊是自旋 + CAS的操作
doReleaseShared
/**
* Release action for shared mode -- signals successor and ensures
* propagation. (Note: For exclusive mode, release just amounts
* to calling unparkSuccessor of head if it needs signal.)
*/
private void doReleaseShared() {
/*
* Ensure that a release propagates, even if there are other
* in-progress acquires/releases. This proceeds in the usual
* way of trying to unparkSuccessor of head if it needs
* signal. But if it does not, status is set to PROPAGATE to
* ensure that upon release, propagation continues.
* Additionally, we must loop in case a new node is added
* while we are doing this. Also, unlike other uses of
* unparkSuccessor, we need to know if CAS to reset status
* fails, if so rechecking.
*/
for (;;) {
// 自旋
// 記錄頭節點
Node h = head;
if (h != null && h != tail) {
// 頭節點不爲null,且不等於尾結點,說明隊列中還有節點
// 獲取頭節點等待狀態
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
// 頭節點等待狀態是SIGNAL
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
// 如果修改節點等待狀態失敗,進入下一次循環
continue; // loop to recheck cases
// 修改成功後,喚醒後繼節點,unparkSuccessor前文講過
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
總結
AQS
可以說是整個併發編程中最難的一個類。但是理解AQS
的實現卻非常重要,因爲它是JDK中鎖和其他同步工具實現的基礎。