AbstractQueuedSynchronizer框架淺析
1.概述
AbstractQueuedSynchronizer(AQS)抽象類提供一個實現阻塞鎖和依賴于FIFO等待隊列的同步器的框架。
AQS被設計用來作為眾多同步器的基類,例如ReentrantLock、Semaphore、CountDownLatch、FutureTask以及ReentrantReadWriteLock。AQS依賴于一個整數值,用來代表狀態。AQS的子類可以通過覆寫protect方法來改變狀態,并且定義狀態代表具體什么含義。不同的同步器對狀態的含義解釋不同:
- 在ReentrantLock中,AQS的同步狀態用于保存鎖獲取操作的次數;
- 在FutureTask中,AQS同步狀態被用來保存任務的狀態;
- 在Semaphore和CountDownLatch中,AQS的同步狀態用于保存當前可用許可的數量;
針對AQS中的狀態,只可以通過AQS的getState()、setState()和compareAndSetState()方法來改變狀態。
同步器類應該使用私有的AQS子類來實現功能,而不應該直接繼承AQS類。AQS類沒有實現任何同步接口,它只是定義了一些可以被具體同步器和鎖調用的方法,例如acquireInterruptibly方法。AQS子類需要自己實現以下方法:
- tryAcquire()
- tryRelease()
- tryAcquireShared()
- tryReleaseShared()
- isHeldExclusively()
這些方法默認會拋出UnsupportedOperationException異常。在這些方法實現中,可以使用getState()、setState()和compareAndSetState()來獲取或修改AQS的狀態。這些方法的實現必須是線程安全的,并且應該實現簡單且沒有阻塞。只有這些方法可以被子類覆寫,其他的AQS的公開方法都是final方法。
AQS同時支持獨占操作模式(例如ReentrantLock)和非獨占操作模式(例如Semaphore和CountDownLatch)。當以獨占模式獲取鎖時,只有一個線程能訪問成功,其他線程都訪問失??;而以非獨占模式獲取鎖時,多個線程可以同時訪問成功。不同操作模式的線程都在同一個FIFO隊列中等待。通常,AQS的子類只支持一種操作模式(獨占或非獨占),但也有同時支持兩種操作模式的同步器,例如ReadWriteLock的子類,它的讀取鎖是非獨占操作模式,而寫入鎖是獨占操作模式。
由于在將線程放入FIFO等待隊列之前,需要嘗試一次acquire,因此有可能新的acquire線程可以獲取成功,盡管等待隊列中還有其他線程阻塞等待,這是一種非公平策略。然而,可以在tryAcquire()或者tryAcquireShared()方法中禁止線程搶占,具體是通過hasQueuedPredecessors()方法判斷等待隊列中是否有線程在阻塞等待,如果有線程阻塞等待,則讓tryAcquire()或者tryAcquireShared()方法返回false,這樣的話,acquire線程會被放入等待隊列的尾部,然后喚醒阻塞等待的線程,這是一種公平的策略。
AQS類為同步器的狀態、參數的獲取和釋放,以及內部FIFO等待隊列,提供了一個高效的和可擴展的基礎。當這些不能滿足你的要求時,你可以自定義java.util.concurrent.atomic原子類,自定義java.util.Queue類,以及LockSupport類來提供阻塞支持。
AQS框架的一個類圖如下所示:
2.源碼分析
2.1 AbstractQueuedSynchronizer類的繼承關系
AQS類繼承了AbstractOwnableSynchronizer類,實現了Serializable接口。AbstractOwnableSynchronizer類主要是用來保存同步器被哪個線程獨占使用。
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
......
}
public abstract class AbstractOwnableSynchronizer
implements java.io.Serializable {
protected AbstractOwnableSynchronizer() { }
//The current owner of exclusive mode synchronization.
private transient Thread exclusiveOwnerThread;
protected final void setExclusiveOwnerThread(Thread thread) {
exclusiveOwnerThread = thread;
}
protected final Thread getExclusiveOwnerThread() {
return exclusiveOwnerThread;
}
}
2.2 Node內部類
在AQS類中,定義了一個FIFO等待隊列節點的內部類Node。
static final class Node {
/** Marker to indicate a node is waiting in shared mode */
static final Node SHARED = new Node();
/** Marker to indicate a node is waiting in exclusive mode */
static final Node EXCLUSIVE = null;
/** waitStatus value to indicate thread has cancelled */
static final int CANCELLED = 1;
/** waitStatus value to indicate successor's thread needs unparking */
static final int SIGNAL = -1;
/** waitStatus value to indicate thread is waiting on condition */
static final int CONDITION = -2;
/**
* waitStatus value to indicate the next acquireShared should
* unconditionally propagate
*/
static final int PROPAGATE = -3;
/*
* 非負值意味著該節點不需要signal。
* 該值默認初始化為0,表示普通的同步節點,主要是通過CAS方法來修改該變量值。
*/
volatile int waitStatus;
/*
* 指向前繼節點,當前節點需要依賴于前繼節點來檢查waitStatus。
* 在入隊列的時候賦值,在出隊列的時候為null。
*/
volatile Node prev;
/*
* 指向后繼節點,當前節點依賴后繼節點來喚醒釋放
* 在入隊列的時候賦值,在出隊列的時候為null。
*/
volatile Node next;
/*
* 將該節點入隊列的線程,在構造節點的時候初始化,使用完之后變為null
*/
volatile Thread thread;
/*
* 指向下一個在條件等待,或者是特定的SHARED值的節點。
* 條件隊列只有在獲取獨占鎖時才可以被訪問,我們需要一個單鏈表隊列來保存節點,當他們在條件上等待時。
*/
Node nextWaiter;
/**
* Returns true if node is waiting in shared mode.
*/
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;
}
}
等待隊列是CLH鎖隊列的變種,CLH鎖通常被用來實現自旋鎖。在節點中的waitStatus域保存了線程是否需要被阻塞的信息。當一個節點的前繼節點被釋放了,節點將會收到通知。在等待隊列中的每一個節點,都充當著特定通知形式的監控器,并且持有一個等待線程。節點中的waitStatus域并不控制線程是否能獲取到鎖。如果一個線程是隊列中的第一個線程,并不能保證它能競爭成功獲取到鎖,而只是給予了它參與競爭的權利。
為了將一個Node節點放入到LCH鎖隊列中,只需要將該Node節點拼接到隊列尾部就行;如果為了出隊列,則只需設置隊列的頭指針位置head。
+------+ prev +-----+ +-----+
head | | <---- | | <---- | | tail
+------+ +-----+ +-----+
插入節點到LCH隊列中,只需一個在tail域上的原子操作。相似的,出隊列僅僅需要更新head域,但是出隊列還需要做一些額外的工作,來決定新的head節點的后繼節點是哪個,其中需要考慮的因素有線程是否被取消了,操作是否超時了以及線程是否被中斷了。
在Node節點中的prev域主要用來處理節點被取消的情況。如果一個Node節點被取消了,那邊它的后繼節點需要重新連接到一個未被取消的前繼節點。
在Node節點中的next域主要被用來實現阻塞機制。在每個節點中都保存有線程ID,因此當需要喚醒下一個節點時,只需要通過遍歷next域,找到后繼節點,通過節點獲取到線程ID,從而知道該喚醒哪個線程。
等待狀態只能取以下幾個值:
- SIGNAL:該節點的后繼節點當前是阻塞的,因此當前節點在釋放和取消之后,必須喚醒它的后繼節點。為了避免競爭,acquire方法必須首先進入SIGNAL等著狀態,然后再嘗試原子獲取,這個獲取過程可能會失敗或者阻塞。
- CANCELLED:該節點由于超時或者中斷了被取消了。節點進入了CANCELLED狀態之后,就不會再發生狀態的變化了。特別地,處于CANCELLED狀態節點的線程不會再被阻塞了。
- CONDITION:該節點當前處于一個條件隊列中。經過轉移之后,該節點將會被作為同步隊列的節點使用,此時節點的狀態會被設置為0。
- PROPAGATE:releaseShared方法應該傳遞給其他Node節點。在doReleaseShared方法中,確保傳遞會繼續。
- 0:非以上任何狀態;
等待狀態值使用數值來簡化使用,非負值意味著節點不需要被通知喚醒,因此大多數代碼只需檢查數值的正負就可以知道是否需要喚醒了。
2.3 AQS類的重要成員變量
/*
* 等待隊列的頭節點,延遲初始化
* 除了初始化可以修改head值,還可以通過setHead方法設置
* 只要head節點存在,那么該節點的waitStatus狀態將不會是CANCELLED
*/
private transient volatile Node head;
/*
* 等待隊列的尾節點,延遲初始化
* 只能通過enq方法來添加一個新的等待節點到隊列中,從而來修改tail值
*/
private transient volatile Node tail;
/*
* 同步狀態
*/
private volatile int state;
可以看到,在AQS類中,有三個比較重要的成員變量,其中兩個是表示等待隊列的頭指針和尾指針。另外一個表示同步的狀態。
與state相關的方法有三個:
/*
* 獲取當前同步狀態
*/
protected final int getState() {
return state;
}
/*
* 設置新的同步狀態
*/
protected final void setState(int newState) {
state = newState;
}
/*
* 采用CAS方法來更新同步狀態
* expect 期望的值
* update 更新為新的值
*/
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);//采用了sun.misc.Unsafe類來實現CAS操作
}
在AQS中使用到了sun.misc.Unsafe類來實現CAS操作,Unsafe類的compareAndSwapInt()和compareAndSwapLong()等方法包裝了CAS操作,虛擬機在內部對這些方法做了特殊處理,即時編譯出來的結果就是一條平臺相關的處理器CAS指令。
與head和tail相關的方法有兩個:
/*
* 將隊列的頭結點設置為node結點
* 該方法只被acquire系列方法調用
*/
private void setHead(Node node) {
head = node;//讓head指針指向node結點
node.thread = null;
node.prev = null;
}
/*
* 插入node節點到隊列中,必要時初始化
* 返回node節點的前繼節點,即原來的尾節點
*/
private Node enq(final Node node) {
for (;;) {
Node t = tail;
// 尾節點為空,則先進行初始化操作,創建一個新的node節點
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))//head節點被成功初始化后,將tail節點指向head節點
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {//將tail節點更新為node節點
t.next = node;
return t;//返回node的前繼節點
}
}
}
}
可以看到,head和tail節點的初始化操作是在setHead()和enq()方法中進行的,同時更新操作也是在這兩個方法中進行的。
2.4 AQS類暴露給子類實現的方法
/*
* 嘗試以獨占模式獲取
* 該方法應該查詢對象的狀態是否允許以獨占模式獲取,如果允許,則獲取。
* 該方法總是被執行acquire方法的線程執行,如果該方法失敗了,則acquire方法將該線程放入隊列中,直到有其他線程調用了release方法來發送通知信號。
* 如果獲取成功了,則返回true。
*/
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
/*
* 嘗試修改狀態來反映以獨占模式釋放
* 該方法總是被執行釋放的線程觸發
* 如果該對象處于完全釋放的狀態則返回true
*/
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
/*
* 嘗試以非獨占模式來獲取
* 該方法應該查詢是否對象允許以非獨占模式來獲取,如果允許,則獲取。
* 該方法總是被執行acquire方法的線程執行。如果該方法失敗了,則acquire方法將該線程放入隊列中,直到有其他線程調用了release方法來發送通知信號
* 返回值為負值,表示失??;
* 返回0,表示以非獨占模式獲取成功,但后續的以非獨占模式獲取將失敗
* 返回正值,表示以非獨占模式獲取成功,后續的以非獨占模式獲取也會成功
*/
protected int tryAcquireShared(int arg) {
throw new UnsupportedOperationException();
}
/*
* 嘗試修改狀態來反映以非獨占模式釋放
* 該方法總是被執行釋放的線程觸發
* 返回true,表示以非獨占模式釋放成功
*/
protected boolean tryReleaseShared(int arg) {
throw new UnsupportedOperationException();
}
/*
* 如果同步器是被當前線程以獨占模式訪問,則返回true。
* 該方法在每次調用非等待ConditionObject方法時被觸發。
*/
protected boolean isHeldExclusively() {
throw new UnsupportedOperationException();
}
2.5 AQS類定義的acquire系列方法
2.5.1 acquire()方法
/*
* 以獨占模式獲取,屏蔽中斷
* 至少觸發一次tryAcquire()方法,如果獲取成功,直接返回
* 如果獲取失敗,則線程入隊列,然后通過tryAcquire()不斷嘗試,直至成功
* 該方法可以被用來實現Lock.lock()方法
*/
public final void acquire(int arg) {
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
tryAcquire()方法是空實現,需要子類覆寫該方法,實現具體的獲取操作。addWaiter()方法主要是為當前線程創建一個新的Node節點,并把該Node節點以指定的模式存放入隊列中。
/*
* 為當前線程創建一個Node節點,并且設置為指定mode,最后將該node節點放入等待隊列中
* @param mode mode有兩種取值:Node.EXCLUSIVE和Node.SHARED,分別代表以獨占模式和非獨占模式
* @return 返回新的Node節點
*/
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);//給當前線程創建一個新的Node節點,節點模式為mode
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;//將node節點的前繼指針指向尾節點
if (compareAndSetTail(pred, node)) {//更新tail指針指向node節點
pred.next = node;//將原來tail節點的后繼指針指向node節點
return node;//這樣node節點成功鏈接到tail節點后面,并更新tail節點指向node節點了
}
}
// 如果前面將node節點入隊列失敗,則再通過enq()方法入隊列,其實現思想和上面的過程一致
enq(node);
return node;
}
addWaiter()方法首先根據mode模式給當前線程創建一個node節點,然后將該node節點放入隊列的尾部。acquireQueued()方法以獨占不可中斷方式獲取。
/*
* 為隊列中的線程,以獨占不可中斷模式獲取
* 該方法被條件等待和acquire方法使用
* @param node 節點
* @param arg 獲取的參數
* @return 在等待的過程中被中斷了,則返回true
*/
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)) {//如果前繼節點等于head節點,并且嘗試獲取成功
setHead(node);//更新head節點為node節點
p.next = null; // help GC
failed = false;
return interrupted;//返回是否中斷了
}
// 在獲取失敗之后,判斷是否需要掛起該線程,如果需要掛起,則通過LockSupport.lock()方法掛起該線程,等線程被喚醒后判斷是否被中斷過
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)//發生了異常,則取消獲取操作
cancelAcquire(node);//取消獲取操作
}
}
在acquireQueued()方法中,從尾節點開始循環前向遍歷,如果當前節點的前繼節點是頭節點,并且tryAcquire()方法返回true了,則更新頭結點,并返回。如果在向前遍歷的過程中,遇到了節點獲取失敗需要掛起時,則會通過LockSupport的park()將當前線程掛起。
/*
* 檢查和更新獲取失敗節點的狀態
* 如果線程應該被阻塞,則返回true
* 該方法需要滿足pred == node.prev
* @param pred node節點的前繼節點,保存狀態
* @param node node節點
* @return 如果線程應該被阻塞,則返回true
*/
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;//獲取前繼節點的等待狀態
if (ws == Node.SIGNAL)// SIGNAL狀態
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
if (ws > 0) {//CANCELLED狀態
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;//向前遍歷搜索,找出前繼節點
} while (pred.waitStatus > 0);
pred.next = node;
} else {// waitStatus為0或者為PROPAGATE
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);//設置waitStatus為SIGNAL
}
return false;
}
在shouldParkAfterFailedAcquire()方法中,根據pred節點的等待狀態做出相應的處理:
- SIGNAL狀態:說明該節點已經更新了狀態,請求釋放,因此可以返回true
- CANCELLED狀態:說明節點已經被取消了,忽略前繼節點狀態為CANCELLED的節點,同時更新node和pred節點值
- PROPAGATE狀態或0:說明節點需要更新狀態為SIGNAL。
可以看到,shouldParkAfterFailedAcquire()方法只有節點等待狀態為SIGNAL時,才會返回true。后續才會執行parkAndCheckInterrupt()方法。
/*
* 停止當前線程參與系統調度,即掛起當前線程,并返回當前線程是否被中斷了
*/
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);//掛斷當前線程
return Thread.interrupted();//返回線程是否被中斷了
}
LockSupport.park()方法:
/*
* 掛起線程
* 當發生了以下幾種情況,可以喚醒線程:
* 1.其他線程觸發了LockSupport.unlock()方法,喚醒線程
* 2.其他線程觸發了Thread.interrupt()方法,打斷當前線程
* 3.該調用無條件返回了
* 該方法不會記錄什么原因導致該方法返回了,因此方法調用者,需要自己重新檢查導致線程掛起的條件
* @param blocker 導致線程掛起的同步器
*/
public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(false, 0L);
setBlocker(t, null);
}
從前面的一些方法調用可以看到,acquire方法主要分為兩種情況來處理:
- 第一次通過tryAcquire()方法獲取成功了,則直接返回;
- 當第一次tryAcquire()失敗時,接下來為當前線程創建一個以獨占操作模式的Node節點,并把Node節點放入等待隊列的尾部。然后在acquireQueued()方法中,從尾節點開始向前,循環從等待隊列中取出節點,判斷是否允許獲取操作。如果不允許獲取,即需要阻塞,則通過LockSupport.lock()方法掛起當前線程。當其他線程解除了當前線程的阻塞,或者是發生了中斷,則返回繼續下一個循環。最終只有遍歷到頭結點head,并且tryAcquire()方法返回true時,才會從acquireQueued()方法中退出,代表獲取完成了。
2.5.2 acquireInterruptibly()方法
/*
* 以獨占模式獲取,如果發生了中斷,則停止獲取
* 在獲取之前,首先檢查線程是否被中斷過,然后至少嘗試一次tryAcquire()方法
* 如果第一次tryAcquire()方法失敗,則將線程放入等待隊列中,然后循環調用tryAcquire()方法,直至返回成功或者線程被中斷了
* 該方法可以被用來實現Lock.lockInterruptibly()
*/
public final void acquireInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())//如果線程被中斷了,則直接返回異常
throw new InterruptedException();
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}
當tryAcquire()失敗時,則調用doAcquireInterruptibly()方法重復的獲取。
/*
* 以獨占可中斷模式獲取
*/
private void doAcquireInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.EXCLUSIVE);//給當前線程,創建一個獨占模式的節點,并把節點放入到等待隊列尾部
boolean failed = true;
try {
for (;;) {//從尾節點開始循環處理
final Node p = node.predecessor();//當前節點的前繼節點
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return;
}
// 在獲取失敗之后,判斷是否需要掛起該線程,如果需要掛起,則通過LockSupport.lock()方法掛起該線程,等線程被喚醒后判斷是否被中斷過
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
throw new InterruptedException();// 如果線程被中斷過,則拋出異常
}
} finally {
if (failed)
cancelAcquire(node);
}
}
可以看到doAcquireInterruptibly()方法與acquireQueued()實現大致相似,唯一的不同之處是,doAcquireInterruptibly()檢測到線程被中斷之后,會拋出一個中斷異常。
2.5.3 acquireShared()方法
/*
* 以非獨占模式獲取,屏蔽中斷
* 至少會觸發調用一次tryAcquireShared()
* 如果調用tryAcquireShared()失敗了,則將該線程放入等待隊列中,并且會不斷的嘗試tryAcquireShared()方法,直到返回成功
*/
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
在tryAcquireShared()方法獲取失敗,會調用doAcquireShared()繼續重復的獲取。
/*
* 以非獨占不可中斷模式獲取
*/
private void doAcquireShared(int arg) {
final Node node = addWaiter(Node.SHARED);//為當前線程創建一個非獨占模式的Node節點,并把給Node節點放入到等待隊列的尾部
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {//從尾節點開始循環,不斷向前遍歷節點
final Node p = node.predecessor();
if (p == head) {//遍歷到頭節點
int r = tryAcquireShared(arg);
if (r >= 0) {//獲取成功了,直接返回
setHeadAndPropagate(node, r);//更新頭結點和同步狀態
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
// 在獲取失敗之后,判斷是否需要掛起該線程,如果需要掛起,則通過LockSupport.lock()方法掛起該線程,等線程被喚醒后判斷是否被中斷過
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
doAcquireShared()方法與acquireQueued()方法實現類似,唯一不同之處是獲取成功之后,會調用setHeadAndPropagate()方法來更head節點以及同步狀態。
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // Record old head for check below
setHead(node);
/*
* Try to signal next queued node if:
* Propagation was indicated by caller,
* or was recorded (as h.waitStatus either before
* or after setHead) by a previous operation
* (note: this uses sign-check of waitStatus because
* PROPAGATE status may transition to SIGNAL.)
* and
* The next node is waiting in shared mode,
* or we don't know, because it appears null
*
* The conservatism in both of these checks may cause
* unnecessary wake-ups, but only when there are multiple
* racing acquires/releases, so most need signals now or soon
* anyway.
*/
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared())
doReleaseShared();
}
}
在以下幾種情況下,會對Node節點的后繼節點進行判斷是否需要釋放:
- propagate大于0;
- 之前的頭結點head為空;
- 之前的頭結點head的waitStatus狀態小于0;
- 當前的頭結點head為空;
- 當前的頭結點head的waitStatus狀態小于0;
在滿足以上幾種情況后,如果后繼節點為空,或者后繼節點是非獨占模式的,則執行釋放操作。
/*
* 非獨占模式的釋放
* 通知后繼節點并且確保釋放的傳遞
*/
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) {
int ws = h.waitStatus;//等待狀態
if (ws == Node.SIGNAL) {//等待狀態為SIGNAL
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))//1.將Node節點的狀態更新為0
continue; // loop to recheck cases
unparkSuccessor(h);//喚醒h節點的后繼節點
}else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))//2.將Node節點的狀態更新為PROPAGATE
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
可以看到,在doReleaseShared()方法中,主要的工作有:
先將頭節點從SIGNAL狀態更新為0,并且喚醒頭結點的后繼節點;
將頭節點的狀態從0更新為PROPAGATE;
-
如果頭結點在更新狀態的時候沒有發生改變,則退出循環;
/*
-
喚醒Node的后繼節點
/
private void unparkSuccessor(Node node) {
/- If status is negative (i.e., possibly needing signal) try
- to clear in anticipation of signalling. It is OK if this
- fails or if status is changed by waiting thread.
*/
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.
*/
Node s = node.next;//Node的后繼節點
if (s == null || s.waitStatus > 0) {// 后繼節點為空,或者后繼節點的等待狀態大于0,則嘗試從尾部節點開始到Node節點為止,尋找最靠近Node節點的等待狀態小于等于0的節點
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
// 存在后繼節點,其等待狀態小于等于0
if (s != null)
LockSupport.unpark(s.thread);//喚醒該節點關聯的線程
}
-
在unparkSuccessor()方法中,主要是喚醒Node的后繼節點中等待狀態小于等于0的節點。
2.5.4 acquireSharedInterruptibly()方法
/*
* 以非獨占模式獲取,如果發生了中斷,則停止獲取
* 在獲取之前,首先檢查線程是否被中斷過,然后至少嘗試一次tryAcquireShared()方法
* 如果調用tryAcquireShared()失敗了,則將該線程放入等待隊列中,并且會不斷的嘗試tryAcquireShared()方法,直到返回成功
*/
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())//線程被中斷了
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
在tryAcquireShared()返回失敗后,會調用doAcquireSharedInterruptibly()方法重復嘗試獲取。
/*
* 以非獨占可中斷模式獲取
*/
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();//獲取前繼節點
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
// 在獲取失敗之后,判斷是否需要掛起該線程,如果需要掛起,則通過LockSupport.lock()方法掛起該線程,等線程被喚醒后判斷是否被中斷過
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
throw new InterruptedException();//拋出中斷異常
}
} finally {
if (failed)
cancelAcquire(node);
}
}
可以看到doAcquireSharedInterruptibly()方法與doAcquireShared()方法實現類似,唯一的不同之處是,在線程等待的過程中,如果被中斷了,則會拋出中斷異常。
2.5.5 tryAcquireNanos()
/*
* 嘗試以獨占模式獲取,如果發生了中斷則停止,如果超時了,則返回失敗
* 在獲取之前,首先檢查線程是否被中斷過,然后至少嘗試一次tryAcquire()方法
* 如果調用tryAcquire()失敗了,則將該線程放入等待隊列中,并且會不斷的嘗試tryAcquire()方法,直到返回成功,或者被中斷,或者超時了。
* 該方法可以被用來實現Lock.tryLock(long,TimeUnit)方法
*/
public final boolean tryAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())//檢查線程是否被中斷了
throw new InterruptedException();
return tryAcquire(arg) ||
doAcquireNanos(arg, nanosTimeout);
}
當tryAcquire()方法返回失敗時,會去調用doAcquireNanos()方法,重復嘗試獲取。
/*
* 以獨占超時模式獲取
*/
private boolean doAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (nanosTimeout <= 0L)// 如果超時時間小于0,則直接返回
return false;
final long deadline = System.nanoTime() + nanosTimeout;//計算截止時間
final Node node = addWaiter(Node.EXCLUSIVE);//為當前線程創建一個獨占模式的Node節點,并把Node放入到等待隊列中
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();//前繼節點
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return true;
}
nanosTimeout = deadline - System.nanoTime();//計算剩余時間
if (nanosTimeout <= 0L)//超時了,直接返回
return false;
if (shouldParkAfterFailedAcquire(p, node) &&
nanosTimeout > spinForTimeoutThreshold)//在獲取失敗后,如果需要讓線程掛起,則通過LockSupport的parkNanos()方法,讓線程掛起指定的時間
LockSupport.parkNanos(this, nanosTimeout);
if (Thread.interrupted())//如果線程被中斷了,則拋出異常
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
doAcquireNanos()方法與acquireQueued()方法實現很類似,不同之處在于,doAcquireNanos()加了一個超時判斷,如果超時了,則直接返回。另外,doAcquireNanos()使用帶超時時間的LockSupport.parkNanos()方法來暫停線程。
2.5.6 tryAcquireSharedNanos()方法
/*
* 嘗試以非獨占模式獲取,如果發生了中斷,則停止,如果超時了,則返回失敗
* 在獲取之前,首先檢查線程是否被中斷過,然后至少嘗試一次tryAcquireShared()方法
* 如果調用tryAcquireShared()失敗了,則將該線程放入等待隊列中,并且會不斷的嘗試tryAcquireShared()方法,直到返回成功,或者被中斷,或者超時了。
*/
public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())//線程被中斷了
throw new InterruptedException();
return tryAcquireShared(arg) >= 0 ||
doAcquireSharedNanos(arg, nanosTimeout);
}
當tryAcquireShared()方法返回失敗時,會去調用doAcquireSharedNanos()不斷重復嘗試獲取。
/*
* 以非獨占超時模式獲取
*/
private boolean doAcquireSharedNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (nanosTimeout <= 0L)// 如果超時時間小于0,則直接返回
return false;
final long deadline = System.nanoTime() + nanosTimeout;//計算截止時間
final Node node = addWaiter(Node.SHARED);//為當前線程創建一個非獨占模式的Node節點,并把Node放入到等待隊列中
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();//前繼節點
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return true;
}
}
nanosTimeout = deadline - System.nanoTime();//計算剩余時間
if (nanosTimeout <= 0L)//超時了,直接返回
return false;
if (shouldParkAfterFailedAcquire(p, node) &&
nanosTimeout > spinForTimeoutThreshold) //在獲取失敗后,如果需要讓線程掛起,則通過LockSupport的parkNanos()方法,讓線程掛起指定的時間
LockSupport.parkNanos(this, nanosTimeout);
if (Thread.interrupted())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
doAcquireSharedNanos()方法與doAcquireNanos()方法實現類似,唯一的區別是doAcquireSharedNanos()方法中是以非獨占模式去獲取狀態,調用的是tryAcquireShared()方法去獲取狀態。
至此,已經介紹了所有公開的與acquire相關的final方法。下面看看所有公開的與release相關的final方法。
2.6 AQS類里面定義的release方法
2.6.1 release()方法
/*
* 以獨占模式釋放
* 如果tryRelease()方法返回true,則至少有一個線程非阻塞
* 該方法可以用來實現Lock.unlock()方法
*/
public final boolean release(int arg) {
if (tryRelease(arg)) {//tryRelease()返回true,則返回true
Node h = head;//頭結點
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);//喚醒頭結點的后繼節點
return true;
}
return false;
}
可以看到,在release()方法中,首先會調用tryRelease()方法嘗試釋放,如果釋放成功,則返回true;如果釋放失敗,則直接返回false。在tryRelease()方法返回成功后,還會根據頭結點的等待狀態來判斷是否需要喚醒頭結點的后繼節點。
2.6.2 releaseShared()方法
/*
* 以非獨占模式釋放
* 如果tryReleaseShared()方法返回true,則至少有一個線程非阻塞
*/
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {//tryReleaseShared()返回true,則返回true
doReleaseShared();
return true;
}
return false;
}
doReleaseShared()方法在5.3中的acquireShared()方法中有介紹到,其主要是先將頭節點從SIGNAL狀態更新為0,并且喚醒頭結點的后繼節點,然后將頭節點的狀態從0更新為PROPAGATE。
3.AQS的使用
我們通常不是直接繼承AQS類,而是將相應的功能委托為私有的AQS子類來實現。下面是AQS類源碼中介紹的兩個使用范例:
- 使用范例1
下面是一個不可重入互斥鎖Mutex,使用0代表非鎖定狀態,使用1代表鎖定狀態。雖然不可重入鎖不需要嚴格記錄持有鎖的當前線程,但是在Mutex類中,實現了記錄當前持有鎖的線程,這樣更容易監控。另外,Mutex類也支持Condition條件,并且暴露了一些方法給外部使用。
public class Mutex implements Lock ,Serializable {
private static class Sync extends AbstractQueuedSynchronizer{
// Reports whether in locked state
@Override
protected boolean isHeldExclusively() {
return getState() == 1;
}
// Acquires the lock if state is zero
@Override
protected boolean tryAcquire(int acquires) {
assert acquires == 1;
if (compareAndSetState(0,1)){
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
// Releases the lock by setting state to zero
@Override
protected boolean tryRelease(int releases) {
assert releases == 1;
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();
@Override
public void lock() {
sync.acquire(1);
}
@Override
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
@Override
public boolean tryLock() {
return sync.tryAcquire(1);
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1,unit.toNanos(time));
}
@Override
public void unlock() {
sync.release(1);
}
@Override
public Condition newCondition() {
return sync.newCondition();
}
public boolean isLocked(){
return sync.isHeldExclusively();
}
public boolean hasQueuedThreads(){
return sync.hasQueuedThreads();
}
}
- 使用范例2
下面是一個實現類似閉鎖CountDownLatch功能的類,它是以非獨占模式獲取和釋放。
public class BooleanLatch {
private static class Sync extends AbstractQueuedSynchronizer{
boolean isSignalled(){
return getState() != 0;
}
@Override
protected int tryAcquireShared(int ignore) {
return isSignalled() ? 1 : -1;
}
@Override
protected boolean tryReleaseShared(int ignore) {
setState(1);
return true;
}
}
private final Sync sync = new Sync();
public boolean isSignalled(){
return sync.isSignalled();
}
public void signal(){
sync.releaseShared(1);
}
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
}