AbstractQueuedSynchronizer簡稱AQS,即隊列(CLH隊列)同步器。它是構建鎖或者其他同步組件的基礎框架(如ReentrantLock、ReentrantReadWriteLock、Semaphore等),JUC併發包的作者(Doug Lea)期望它能夠成爲實現大部分同步需求的基礎。它是JUC併發包中的核心基礎組件
下面先簡單的介紹下AQS中比較重要的方法
//同步狀態,使用者可以對這個狀態進行自定義,一般情況下可以定義爲
//0表示沒有競爭,線程可以直接獲取鎖
//大於0的時候鎖正在被佔用
//對於可重入的獨佔鎖,可以表示爲重入的次數
//對於有憑證數量的共享鎖,可以表示爲憑證數量,獲取一個減去1
private volatile int state;
//使用CAS設置state狀態,該方法能夠保證狀態設置的原子性;
protected final boolean compareAndSetState(int expect, int update)
//獨佔式嘗試獲取同步狀態,需要使用者重寫,返回true表明獲取成功
protected boolean tryAcquire(int arg)
//獨佔式嘗試釋放同步狀態,需要使用者重寫
protected boolean tryRelease(int arg)
//共享式嘗試獲取同步狀態,需要使用者重寫,返回大於等於0表示獲取成功
protected int tryAcquireShared(int arg)
//共享式嘗試釋放同步狀態,需要使用者重寫
protected boolean tryReleaseShared(int arg)
//獨佔式獲取同步狀態,會調用tryAcquire(int arg)嘗試獲取,如果失敗將會將當前線程加入同步隊列
public final void acquire(int arg)
//與acquire(int arg)功能相同,該操作可被中斷,拋出InterruptedException
public final void acquireInterruptibly(int arg)
//獨佔式獲取同步狀態,帶上超時機制,如果nanosTimeout納秒內獲取同步狀態返回true,否則返回false
public final boolean tryAcquireNanos(int arg, long nanosTimeout)
//共享式獲取同步狀態,因爲不是獨佔式的,所以可以有多個線程獲取同步狀態
public final void acquireShared(int arg)
//共享式獲取同步狀態,可中斷
public final void acquireSharedInterruptibly(int arg)
//共享式獲取同步狀態,帶上超時機制
public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)
//獨佔式釋放同步狀態,會調用tryRelease(int arg),如果失敗返回false,成功將會喚醒隊列中的第二個節點,第一個是頭節點(一個空節點)
public final boolean release(int arg)
//共享式釋放同步狀態,會調用tryAcquireShared(int arg),如果失敗返回false,成功將會喚醒隊列中的第二個節點
public final boolean releaseShared(int arg)
AQS是一個隊列同步器,它的工作只是在併發的環境下處理線程(同步狀態競爭失敗的線程)入隊、線程掛起、線程喚醒、線程出隊, 而涉及到需要同步獲取狀態的邏輯需要使用者自己去編寫
以獨佔模式爲例,獲取同步狀態的過程
從上圖可以看出同步器的設計是基於模版方法模式的,也就是說,使用者需要繼承同步器並重寫指定的方法,隨後將同步器組合在自定義同步組件的實現中,並調用同步器提供的模版方法,而這些模版方法將會調用使用者重寫的方法
在上圖中模版方法是acquire
用戶需要重寫的方法是tryAcquire
以獨佔模式爲例,釋放同步狀態的過程
下面開始源碼部分,先看下Node類
//隊列節點,存儲等待同步狀態的線程,以及一些狀態
static final class Node {
//表明該節點中的線程等待的是共享式同步
static final Node SHARED = new Node();
//表明該節點中的線程等待的是獨佔式同步
static final Node EXCLUSIVE = null;
//表明該節點被取消,按照順序檢查需要被喚醒的線程的時候會跳過該節點
static final int CANCELLED = 1;
//因爲是CLH隊列,節點會循環訪問前驅節點的狀態,以確定自身是否可以成功被喚醒,
//當在前驅節點狀態爲SIGNAL時,表示自己可以放心掛起,等待前驅節點釋放同步狀態的時候將自己喚醒
//詳情見shouldParkAfterFailedAcquire
static final int SIGNAL = -1;
//condition時使用,暫時略
static final int CONDITION = -2;
//表示下一個acquireShared應該無條件傳播,具體後面分析
static final int PROPAGATE = -3;
//存儲的線程
volatile Thread thread;
//和condition共用,在這裏表示同步的模式 爲null(獨佔)或者 SHARED(共享)
Node nextWaiter;
}
//隊列頭節點,在這裏頭節點是一個空節點,在線程第一次入隊的時候初始化,
//線程出隊的時候會把自身節點設置成新的頭節點來替代老的頭節點
private transient volatile Node head;
//隊列尾節點
private transient volatile Node tail;
獨佔模式
public final void acquire(int arg) {
//先調用用戶重寫的tryAcquire,如果失敗則將本線程放入同步隊列中
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
//指明需要用戶繼承重寫
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
匹配上面的流程圖,這裏先調用tryAcquire,如果失敗再執行入隊操作
看下入隊操作的源碼
//創建節點,傳入節點的類型,這裏是獨佔模式,所以傳入Node.EXCLUSIVE
private Node addWaiter(Node mode) {
//調用構造方法,賦值當前線程、節點類型
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
//當尾節點不爲空,說明隊列已經初始化過了,那麼直接通過cas將節點加入尾節點
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//如果尾節點爲null 或者 cas失敗了
enq(node);
return node;
}
//隊列未初始化,或者cas入隊失敗的情況下調用該方法
private Node enq(final Node node) {
for (;;) {
Node t = tail;
//如果未初始化隊列,創建head,cas賦值
if (t == null) {
if (compareAndSetHead(new Node()))
tail = head;
} else {
//如果是之前cas入隊失敗,這裏循環cas 直到成功
//需要注意的細節,先賦值prev,再通過cas賦值next,這樣保證了無論cas成功還是失敗,隊列都是完整的
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
入隊完成後,獨佔模式下,需要對已入隊的節點進行一系列的處理
//對已入隊的線程進行循環處理(不響應interrupted)
//1、判斷自己的前驅是否是頭節點,如果是執行tryAcquire,成功則獲取同步狀態出隊,否則2
//2、修改自己前驅狀態爲SIGNAL(如果前驅狀態爲cancel則略過),成功則將自己掛起等待下次喚醒,否則3
//3、如果前面都是否,說明在此期間節點發生了變化,那麼進入循環進行下一輪判斷
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);
p.next = null; // help GC
failed = false;
return interrupted;
}
//修改自己前驅狀態爲SIGNAL,處理cancel節點,如果成功了
//執行parkAndCheckInterrupt,將自己掛起,等待喚醒
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
//如果parkAndCheckInterrupt也是true,說明該線程是通過中斷喚醒的,那麼保存中斷標示
interrupted = true;
}
} finally {
//如果失敗,比如node.predecessor會拋出NullPointerException
if (failed)
//將節點設置成CANCELLED
cancelAcquire(node);
}
}
//修改前驅狀態爲Node.SIGNAL,確保自己被掛起後,前驅能夠叫醒自己
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
//如果已經是SIGNAL,則直接返回
if (ws == Node.SIGNAL)
return true;
//如果>0,說明是cancel,那麼這些節點將會被剔除出隊列
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
//如果前驅狀態不爲SIGNAL,則進行cas修改
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
//如果前驅狀態不爲SIGNAL,則返回進入下一個循環
return false;
}
//需要注意的是park是響應中斷的,這個時候需要將中斷標示保存下來返回
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
//對節點執行取消操作
private void cancelAcquire(Node node) {
if (node == null)
return;
node.thread = null;
//如果前驅節點也是cancel,剔除出隊列
Node pred = node.prev;
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
Node predNext = pred.next;
node.waitStatus = Node.CANCELLED;
//如果是尾節點則通過cas將自身剔除出隊列
if (node == tail && compareAndSetTail(node, pred)) {
compareAndSetNext(pred, predNext, null);
} else {
//如果前驅節點不爲頭節點 而且狀態爲signal 或者可以被cas修改成signal,後繼節點不爲cancel
//則嘗試將前驅節點直接關聯後繼節點,將自身剔除出隊列
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)
compareAndSetNext(pred, predNext, next);
} else {
//如果修改signal失敗,或者爲頭節點,則喚醒後繼節點
//這個時候喚醒對後繼節點會執行shouldParkAfterFailedAcquire
//來剔除cancel節點和設置前驅節點狀態爲SIGNAL
unparkSuccessor(node);
}
node.next = node; // help GC
}
}
到這裏acquire部分就基本說完了,下面來看下獨佔模式下釋放同步狀態
public final boolean release(int arg) {
//這裏tryRelease也是需要使用者自己重寫對
if (tryRelease(arg)) {
Node h = head;
//如果頭節點狀態爲0,說明後面沒有節點了,否則喚醒後繼節點
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
//喚醒node的後繼節點
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
//先要修改節點狀態,正常情況下這裏ws狀態應該是SIGNAL,標示後繼節點需要喚醒
//而這裏已經在進行喚醒操作,所以修改成0
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
//當遇到後繼節點爲空或者爲cancel的情況下
if (s == null || s.waitStatus > 0) {
s = null;
//從tail開始循環往前找最前一個狀態不爲cancel的節點
//至於爲什麼不從前往後找,因爲addWaiter中是先設置node.prev = pred;再設置pred.next = node;
//所以從前往後找,也許找不到這個節點
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
//將後繼節點喚醒,這個時候被喚醒的線程開始繼續走下一個循環,詳細看acquireQueued
if (s != null)
LockSupport.unpark(s.thread);
}
關於acquireInterruptibly, 與acquire的區別是,前者是響應中斷,會拋出InterruptedException
public final void acquireInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}
//和acquireQueued邏輯差不多
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;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
//唯一的區別在這裏,這裏檢測到中斷就會直接拋錯
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
關於tryAcquireNanos,可以理解成帶上超時機制的acquire,而且響應中斷
public final boolean tryAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
return tryAcquire(arg) ||
doAcquireNanos(arg, nanosTimeout);
}
//大致邏輯還是差不多的
private boolean doAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (nanosTimeout <= 0L)
return false;
//先計算超時的時間界限,單位納秒
final long deadline = System.nanoTime() + nanosTimeout;
//添加節點
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 true;
}
//前面嘗試獲取同步狀態失敗後,開始計算剩餘多久超時
nanosTimeout = deadline - System.nanoTime();
//如果時間小於0,說明已經超時了 直接退出
if (nanosTimeout <= 0L)
return false;
//如果剩餘時間大於1000納秒就用超時機制的掛起
//如果剩餘時間小於1000,就直接進入自旋模式,進入下一個循環繼續判斷
if (shouldParkAfterFailedAcquire(p, node) &&
nanosTimeout > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
//響應中斷
if (Thread.interrupted())
throw new InterruptedException();
}
} finally {
//如果超時了,那麼該節點就變成cancel了
if (failed)
cancelAcquire(node);
}
}
這裏基本上將獨佔模式的獲取同步狀態、釋放同步狀態講完了,接下來看看共享模式部分
先來看看共享式獲取同步狀態
//和獨佔模式的區別在於,這裏tryAcquireShared返回的不是boolean值了,小於0被認爲同步失敗
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
//邏輯部分和獨佔模式一樣
private void doAcquireShared(int arg) {
//添加節點模式爲SHARED
final Node node = addWaiter(Node.SHARED);
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;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
//這個方法做了倆件事
//1、設置傳入的節點爲頭節點
//2、查看頭節點的狀態如果是signal或者PROPAGATE,則繼續喚醒下一個節點
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // Record old head for check below
setHead(node);
// 雖然是共享鎖,但是爲了避免不必要的喚醒,
// 只有當頭節點是signal或者PROPAGATE狀態時才喚醒下一個節點
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
//判斷下一個節點是否是共享節點
if (s == null || s.isShared())
//喚醒下一個節點
doReleaseShared();
}
}
共享模式允許多個線程同時獲取同步狀態,所以在隊列中等待的線程在被喚醒並且成功獲取同步狀態的時候,會換新下一個節點, 這就是PROPAGATE的含義
最後來看下doReleaseShared部分
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
//當狀態爲SIGNAL時,表示可以喚醒下一個節點
if (ws == Node.SIGNAL) {
//這裏通過cas設置爲0是爲了防止多個線程同時執行了unparkSuccessor
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue;
//
unparkSuccessor(h);
}
//如果發現已經有線程將狀態設置爲0了,則設置成PROPAGATE
//如果設置不成功則循環繼續設置,也就是說這裏頭節點的狀態最終只會是SIGNAL或者是PROPAGATE
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
//當發現頭節點沒有變過,就可以安全的退出了
if (h == head) // loop if head changed
break;
}
}