隊列同步器AbstractQueuedSynchronizer(以下簡稱同步器),是構建鎖或者其他同步組件的基本框架,使用了一個int成員變量表示同步狀態,通過內置的FIFO隊列來完成獲取資源的線程的排隊工作。
同步器的主要使用方法是繼承,子類通過繼承同步器並實現它的抽象方法來管理同步狀態。
在抽象方法的實現過程中對同步狀態進行更改,需要使用到同步器提供的三個方法:getState()、setState(int newState)和compareAndSetState(int expect,int update)來進行操作,這三個方法可以保證狀態的改變是安全的。
子類被推薦定義爲自定義同步組件的靜態內部類,同步器自身沒有實現任何同步接口,它僅僅是定義了若干同步狀態獲取和釋放方法來供自定義同步組件使用,同步器即可以支持獨佔式獲取同步狀態,也可以支持共享式地獲取同步狀態,這樣方便實現不同類型的同步組件(ReentrantLock、ReentrantReadWriteLock、CountDownLatch等)。
同步器是實現鎖(也可以是任何同步組件)的關鍵:在鎖中聚合同步器,利用同步器實現鎖的語義。
兩者的關係:鎖是面向使用者的,他定義了使用者與鎖交互的接口(比如允許兩個線程並行訪問),隱藏了實現細節;
同步器是面向鎖的實現者,它簡化了鎖的實現方式,屏蔽了同步管理狀態、線程的排隊、等待與喚醒等底層操作。
同步器三個重要屬性
屬性 | 描述 |
private transient volatile Node head |
同步隊列的首節點引用,懶加載 只能通過setHead()方法修改 如果首節點存在,則該節點的waitStatus一定不是CANCELLED |
private transient volatile Node tail |
同步隊列的尾節點引用,懶加載 只能通過enq()方法增加新的節點 |
private volatile int state |
同步狀態值,大於等於0的值;同步狀態爲0時,表示沒有線程獲取鎖。 在排他鎖裏面,表示鎖被一個線程重複獲取的次數。 如果一個線程獲取了同步狀態,則值加1;成功獲取鎖的線程再次獲取鎖,則增加同步狀態值+1 在讀寫鎖裏面,該變量被切分成兩部分,高16位表示讀,低16位表示寫,通過位運算來實現。 讀狀態是所有線程獲取讀鎖次數的總和,而每個線程各自獲取讀鎖的次數只能選擇保存在ThreadLocal中,由線程自己維護。 寫狀態下表示當前線程獲取寫鎖的次數。 |
同步器可以重寫的方法
方法 | 描述 |
protected boolean tryAcquire(int arg) { throw new UnsupportedOperationException(); } |
獨佔式獲取同步狀態,實現該方法需要查詢當前狀態並判斷同步狀態是否符合預期,然後再進行CAS(compareAndSetState())方法設置同步狀態 |
protected boolean tryRelease(int arg) { throw new UnsupportedOperationException(); } |
獨佔式釋放同步狀態,釋放後在同步隊列中的線程就有機會獲取同步狀態; |
protected int tryAcquireShared(int arg) { throw new UnsupportedOperationException(); } |
共享式獲取同步狀態,返回值大於0時表示獲取成功,反之獲取失敗 |
protected boolean tryReleaseShared(int arg) { throw new UnsupportedOperationException(); } |
共享式釋放狀態 |
protected boolean isHeldExclusively() { throw new UnsupportedOperationException(); } |
當前同步器是否在獨佔模式下被線程佔用,一般該方法表示是否被當前線程所獨佔 |
從上面這些方法的默認實現可以看出來,方法是默認拋出不支持該操作的異常UnsupportedOperationException的,因此如果需要實現共享或獨佔式獲取同步狀態,就要實現相應的方法。
- 這些方法的實現必須在內部是線程安全的,並且應簡短且不阻塞。
- 定義這些方法是使用此類的唯一受支持的方式。 所有其他public方法都被聲明爲final,因爲它們不能獨立變化。
同步器對外(對鎖Lock)提供的模板方法
這些方法都是public final的,就像上面說的,不能被重寫。
- public final void acquire(int arg)
獨佔式獲取同步狀態,忽略線程中斷,也就是說如果線程獲取同步狀態失敗後進入同步隊列中,後續對線程進行中斷操作時,線程不會從同步隊列中移出;
如果當前線程獲取同步狀態成功,就從該方法返回,否則加入同步隊列。
該方法會調用重寫的tryAcquire(arg)方法
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
- public final void acquireInterruptibly(int arg) throws InterruptedException
作用與acquire(int arg)相同,但是該方法會響應中斷,當前線程未獲取到同步狀態而進入同步隊列中,如果當前線程被其他線程中斷,則該方法會拋出InterruptedException並返回
該方法會調用重寫的tryAcquire(arg)方法
public final void acquireInterruptibly(int arg) throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}
- public final boolean tryAcquireNanos(int arg, long nanosTimeout) throws InterruptedException
在acquireInterruptibly(int arg)基礎上增加了超時限制,如果當前線程在超時時間內沒有獲取到同步狀態,那麼將會返回false,如果獲取到了返回true。
該方法會調用重寫的tryAcquire(arg)方法
public final boolean tryAcquireNanos(int arg, long nanosTimeout) throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
return tryAcquire(arg) ||
doAcquireNanos(arg, nanosTimeout);
}
- public final void acquireShared(int arg)
共享式的獲取同步狀態,如果當前線程未獲取到同步狀態,將會進入同步隊列等待,與獨佔式獲取的主要區別是在同一時刻可以有多個線程獲取到同步狀態。
該方法會調用需要重寫的tryAcquireShared(arg)方法
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
- public final void acquireSharedInterruptibly(int arg) throws InterruptedException
作用與acquireShared(int arg)方法相同,區別是該方法在當前線程被其他中斷時返回;
該方法會調用需要重寫的tryAcquireShared(arg)方法
public final void acquireSharedInterruptibly(int arg) throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
- public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout) throws InterruptedException
在acquireSharedInterruptibly(int arg)的基礎上增加了超時限制
該方法會調用需要重寫的tryAcquireShared(arg)方法
public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
return tryAcquireShared(arg) >= 0 ||
doAcquireSharedNanos(arg, nanosTimeout);
}
- public final boolean release(int arg)
獨佔式的釋放同步狀態,該方法會在釋放同步狀態後,將同步隊列中第一個節點包含的線程喚醒。
該方法調用了需要重寫的tryRelease(int arg)方法
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
- public final boolean releaseShared(int arg)
共享式的釋放同步狀態
該方法調用了需要重寫的tryReleaseShared(arg)方法
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
- public final Collection<Thread> getQueuedThreads()
獲取在同步隊列上等待的線程集合
public final Collection<Thread> getQueuedThreads() {
ArrayList<Thread> list = new ArrayList<Thread>();
for (Node p = tail; p != null; p = p.prev) {
Thread t = p.thread;
if (t != null)
list.add(t);
}
return list;
}
隊列同步器的節點(Node)
同步隊列與節點
同步器依賴內部的同步隊列(這個隊列是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;
//CANCELLED等待狀態,由於該線程在同步隊列中等待時超時或者中斷此節點被取消,節點進入該狀態將不會變化
static final int CANCELLED = 1;
//SIGNAL等待狀態,後繼節點的線程處於阻塞狀態,因此當前節點的線程如果釋放了同步狀態或者被取消,將會通知後繼節點,使後繼節點的線程得以運行
static final int SIGNAL = -1;
//CONDITION等待狀態,表示節點在等待隊列中,節點線程在等待在Condition上,當其他線程對Condition調用了signal()方法後,該節點將會從等待隊列轉移到同步隊列中,加入到對同步狀態的獲取中
static final int CONDITION = -2;
/**waitStatus value to indicate the next acquireShared should unconditionally propagate*/
//PROPAGATE等待狀態,表示下一次共享模式同步狀態獲取將會無條件地的傳播下去
static final int PROPAGATE = -3;
//等待狀態,初始值是0,狀態的變化
volatile int waitStatus;
//當前節點/線程的前驅節點
//在排隊時分配,出同步隊列的時候會被賦值null,以便GC收集
//當prev節點取消時,我們會尋找一個非取消的節點
volatile Node prev;
//當前節點/線程的後繼節點
//在排隊時分配,在繞過取消的前任時進行調整,在出隊時清零(出於GC的考慮)
volatile Node next;
//該節點封裝的線程,在構造函數裏初始化,用完之後置null
volatile Thread thread;
//連接至下一個等待condition條件的節點,如果是共享模式,該值是SHARED
//我們用一個鏈表隊列linked queue實現等待隊列
//因爲Lock支持多個condition,因此每個condition都包含着一個等待隊列
Node nextWaiter;
//如果當前節點是在共享模式下,返回true
final boolean isShared() {
return nextWaiter == SHARED;
}
//獲取前一個node節點,如果爲null,則拋出NullPointerException
//在使用的時候不會出現null的,因此null的檢測可以被淘汰,但對VM有幫助
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;
}
}
同步器對節點提供的原子操作
//通過native代碼實現的原子操作
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long stateOffset;
private static final long headOffset;
private static final long tailOffset;
private static final long waitStatusOffset;
private static final long nextOffset;
//通過CAS操作設置首節點
private final boolean compareAndSetHead(Node update) {
return unsafe.compareAndSwapObject(this, headOffset, null, update);
}
//通過CAS操作設置尾節點
private final boolean compareAndSetTail(Node expect, Node update) {
return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
}
//通過CAS操作更新等待狀態的值
private static final boolean compareAndSetWaitStatus(Node node,int expect,int update) {
return unsafe.compareAndSwapInt(node, waitStatusOffset, expect, update);
}
//通過CAS操作設置後繼節點
private static final boolean compareAndSetNext(Node node,Node expect,Node update) {
return unsafe.compareAndSwapObject(node, nextOffset, expect, update);
}
隊列同步器的ConditionObject
見另一篇博文 Condition接口,Lock的監視器方法接口
AbstractQueuedSynchronizer(AQS)整體內部結構圖
獨佔式同步狀態的獲取和釋放
//獲取同步狀態
public final void acquire(int arg) {
// 獨佔式獲取同步狀態tryAcquire()方法只能返回0或1因爲只有一個線程可以獲取同步狀態
if (!tryAcquire(arg) &&
// 獲取同步狀態失敗時,將當前線程加入到同步隊列中,節點在隊列中進行自旋獲取同步狀態
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
}
//創建節點並將節點添加到隊尾
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;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
//將節點加入到同步隊列,通過“死循環”保證節點的正確添加
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;
//設置尾節點
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
//進入自旋獲取同步狀態
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
// 當前線程開始自旋並阻塞該線程直到獲取同步狀態
for (;;) {
//獲取前驅節點
final Node p = node.predecessor();
//只有前驅節點是頭節點的節點才能嘗試獲取同步狀態,符合FIFO隊列規則
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//獲取同步狀態失敗後,判斷是否需要阻塞當前線程 && 阻塞當前線程並返回是否中斷
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
//獲取同步狀態失敗後判斷是否需要阻塞當前線程
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
//獲取前驅節點等候狀態
int ws = pred.waitStatus;
//
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
//如果前驅節點取消了,則尋找一個非取消的前驅節點
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
//waitStatus初始狀態爲0,也可能是PROPAGATE,那麼將其設置爲SIGNAL,表示可以向後繼節點傳播狀態
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
//阻塞當前線程並返回線程是否中斷
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
釋放同步狀態
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
//喚醒後續節點
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;
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;
}
//喚醒後續節點的線程
if (s != null)
LockSupport.unpark(s.thread);
}