1.acquireQueued(Node, int)
源碼:
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)) {
// 將當前節點設置為頭結點
setHead(node);
// 說明前繼節點已經釋放掉資源了,將其next置空,以方便虛擬機回收掉該前繼節點
p.next = null; // help GC
// 標識獲取資源成功
failed = false;
// 返回中斷標記
return interrupted;
}
// 若前繼節點不是頭結點,或者獲取資源失敗,
// 則需要通過shouldParkAfterFailedAcquire函數
// 判斷是否需要阻塞該節點持有的線程
// 若shouldParkAfterFailedAcquire函數返回true,
// 則繼續執行parkAndCheckInterrupt()函數,
// 將該線程阻塞並檢查是否可以被中斷,若返回true,則將interrupted標志置於true
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
interrupted = true;
}
} finally {
// 最終獲取資源失敗,則當前節點放棄獲取資源
if (failed)
cancelAcquire(node);
}
}
具體看一下shouldParkAfterFailedAcquire函數:
// shouldParkAfterFailedAcquire是通過前繼節點的waitStatus值來判斷是否阻塞當前節點的線程的
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
// 獲取前繼節點的waitStatus值ws
int ws = pred.waitStatus;
// 如果ws的值為Node.SIGNAL(-1),則直接返回true
// 說明前繼節點完成資源的釋放或者中斷后,會通知當前節點的,回家等通知就好了,不用自旋頻繁地來打聽消息
if (ws == Node.SIGNAL) return true;
// 如果前繼節點的ws值大於0,即為1,說明前繼節點處於放棄狀態(Cancelled)
// 那就繼續往前遍歷,直到當前節點的前繼節點的ws值為0或負數
// 此處代碼很關鍵,節點往前移動就是通過這里來實現的,直到節點的前繼節點滿足
// if (p == head && tryAcquire(arg))條件,acquireQueued方法才能夠跳出自旋過程
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
// 將前繼節點的ws值設置為Node.SIGNAL,以保證下次自旋時,shouldParkAfterFailedAcquire直接返回true
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
parkAndCheckInterrupt()函數則簡單很多,主要調用LockSupport類的park()方法阻塞當前線程,並返回線程是否被中斷過。
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
至此,獨占模式下,線程獲取資源acquire的代碼就跟完了,總結一下過程:
第一步:首先線程通過tryAcquire(arg)嘗試獲取共享資源,若獲取成功則直接返回,若不成功,
則將該線程以獨占模式添加到等待隊列尾部,
tryAcquire(arg)由繼承AQS的自定義同步器來具體實現;
第二步:當前線程加入等待隊列后,會通過acquireQueued方法基於CAS自旋不斷嘗試獲取資源,直至獲取到資源;
第三步:若在自旋過程中,線程被中斷過,acquireQueued方法會標記此次中斷,並返回true。
第四步:若acquireQueued方法獲取到資源后,返回true,則執行線程自我中斷操作selfInterrupt()。
static void selfInterrupt() {
Thread.currentThread().interrupt();
}
2.釋放資源(獨占模式)
講完獲取資源,對應的講一下AQS的釋放資源過程,其入口函數為:
public final boolean release(int arg) {
if (tryRelease(arg)) {
// 獲取到等待隊列的頭結點h
Node h = head;
// 若頭結點不為空且其ws值非0,則喚醒h的后繼節點
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
總結:邏輯並不復雜,通過tryRelease(arg)來釋放資源,和tryAcquire類似,tryRelease也是有繼承AQS的自定義同步器來具體實現。
函數:
tryRelease(int) --該方法嘗試釋放指定量的資源。
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
學習來源:https://www.jianshu.com/p/0f876ead2846
