在java.util.concurrent
包中,大部分的同步器都是基於AbstractQueuedSynchronizer(AQS)
這個框架實現的。這個框架為同步狀態提供原子性管理、執行緒的阻塞和解除阻塞以及排隊提供了一種通用機制。
同步器一般包含2種方法,一種是acquire
,另一種是release
。acquire
操作阻塞執行緒,獲取鎖。release
通過某種方式改變讓被acquire
阻塞的執行緒繼續執行,釋放鎖。為了實現這2種操作,需要以下3個基本元件的相互協作:
- 同步狀態的原子性管理
- 執行緒的阻塞和解除阻塞
- 佇列管理
同步狀態
/**
* The synchronization state.
*/
private volatile int state;複製程式碼
AQS
使用一個int
變數來儲存同步狀態,並暴露出getState
、setState
以及compareAndSet
來讀取或更新這個狀態。並且用了volatile
來修飾,保證了在多執行緒環境下的可見性。通過使用compare-and-swap(CAS)
指令來實現compareAndSetState
。
這裡的同步狀態用int
而非long
,主要是因為64位long
欄位的原子性操作在很多平臺上是使用內部鎖的方式來模擬實現的,這會使得同步器的會有效能問題。絕對多數int
型的state
足夠我們使用,但JDK
也提供了long
型state
的實現:java.util.concurrent.locks.AbstractQueuedLongSynchronizer
。
阻塞
JDK1.5
之前,阻塞執行緒和解除執行緒阻塞都是基於Java
自身的監控器。在AQS
中實現阻塞是用java.util.concurrent
包的LockSuport類。方法LockSupport.park
阻塞當前執行緒,直到有個LockSupport.unpark
方法被呼叫。
佇列管理
AQS
框架關鍵就在於如何管理被阻塞的執行緒佇列。提供了2個佇列,分別是執行緒安全Sync Queue(CLH Queue)
、普通的Condition Queue
。
Sync Queue
Sync Queue
是基於FIFO
的佇列,用於構建鎖或者其他相關同步裝置。CLH
鎖可以更容易地去實現取消(cancellation)
和超時
功能,因此我們選擇了CLH
鎖作為實現的基礎。
佇列中的元素Node
是儲存執行緒的引用和執行緒狀態。Node
是AQS
的一個靜態內部類:
static final class Node {
static final Node SHARED = new Node();
static final Node EXCLUSIVE = null;
static final int CANCELLED = 1;
static final int SIGNAL = -1;
static final int CONDITION = -2;
static final int PROPAGATE = -3;
volatile int waitStatus;
volatile Node prev;
volatile Node next;
volatile Thread thread;
Node nextWaiter;
}複製程式碼
Node
類的成員變數如上所示,主要負責儲存執行緒引用、佇列的前繼和後繼節點,以及同步狀態:
成員 | 描述 |
---|---|
waitStatus | 用來標記Node的狀態: CANCELLED:1, 表示當前執行緒已經被取消 SIGNAL:-1,表示當前節點的後繼節點等待執行 CONDITION:-2, 表示當前節點已被加入Condition Queue PROPAGATE:-3, 共享鎖的最終狀態是PROPAGATE |
thread | 當前獲取lock的執行緒 |
SHARED | 表示節點是共享模式 |
EXCLUSIVE | 表示節點是獨佔模式 |
prev | 前繼節點 |
next | 後繼節點 |
nextWaiter | 儲存Condition Queue中的後繼節點 |
Node
元素是Sync Queue
構建的基礎。當獲取鎖的時候,請求形成節點掛載在尾部。而鎖資源的釋放再獲取的過程是從開始向後進行的。
acquire 獲取鎖
在AQS
自身僅定義了類似acquire
方法。在實現鎖的時候,一般會實現一個繼承AQS
的內部類Sync
。而在Sync
類中,我們根據需求來實現重寫tryAcquire
方法和tryRelease
方法。獨佔鎖acquire
方法如下:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}複製程式碼
- 通過
tryAcquire(由不同的實現類實現)
嘗試獲取鎖,如果可以獲取鎖直接返回。獲取不到鎖,則呼叫addWaiter
方法;
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;
}
}
}
}複製程式碼
addWaiter
方法作用是把當前執行緒封裝成Node
節點,通過CAS
操作快速嘗試掛載至佇列尾部。- 如果
tail
節點t
已經有了:將t
節點更新為當前節點node
的前繼節點node.prev
,將t.next
更新為當前節點node
; - 如果
tail
節點新增失敗:- 如果
tail
節點為空,那麼原子化的分配一個頭節點,並將尾節點指向頭節點,這一步是初始化; - 如果
tail
節點不為空,迴圈重複addWaiter
方法的工作直至當前節點入隊為止。
- 如果
- 如果
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;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}複製程式碼
- 節點加入
Sync Queue
之後,接下來就是要進行鎖的獲取,或者說是訪問控制了,只有一個執行緒能夠在同一時刻繼續的執行,而其他的進入等待狀態。- 獲取當前節點的前繼節點
- 當前繼節點是頭結點並且能夠獲取狀態,代表該當前節點佔有鎖;如果滿足上述條件,那麼代表能夠佔有鎖,根據節點對鎖佔有的含義,設定頭結點為當前節點。
- 否則進入等待狀態。
至此,可以總結一次acquire
的過程大致為:
release 釋放鎖
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}複製程式碼
- 首先通過
CAS
操作變更同步狀態state
。 - 釋放成功後,通過
LockSupport.unpark
方法來喚醒後繼節點,後繼節點繼續獲取鎖。
Condition Queue
AQS
框架提供了一個ConditionObject
內部類,給維護獨佔同步的類以及實現Lock
介面的類使用。一個鎖物件可以關聯任意數目的條件物件,可以提供典型的Java
監視器風格的await
、signal
和signalAll
操作,包括帶有超時的,以及一些檢測、監控的方法。Condition Queue
是普通的佇列並不要求是執行緒安全,原因是線上程在操作Condition
時,要求執行緒必須獨佔鎖,不需要考慮併發的問題。
Condition Queue
也是以Node
為基礎的佇列。
/** First node of condition queue. */
private transient Node firstWaiter;
/** Last node of condition queue. */
private transient Node lastWaiter;複製程式碼
await操作
Condition
在執行await
操作時,首先會呼叫addConditionWaiter()
方法將當前執行緒封裝的Node
節點加入到wait queue
。
private Node addConditionWaiter() {
Node t = lastWaiter;
// If lastWaiter is cancelled, clean out.
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}複製程式碼
上述addConditionWaiter
的邏輯是:
- 首先清除
Condition Queue
佇列中cancelled
狀態的尾節點; - 如
Condition Queue
佇列為空,封裝當前執行緒的node
節點為Condition Queue
的firstWaiter
。如Condition Queue
佇列不為空,則把該節點加至佇列尾部。
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}複製程式碼
- 加入
Condition queue
之後,要釋放當前執行緒獲取的所有的鎖; - 如果執行緒沒有在
Sync Queue
中,將呼叫LockSupport.park
阻塞當前執行緒,直到signalled
或者interrupted
喚醒去獲取鎖。
single 操作
public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}複製程式碼
- 首先檢查執行緒是否獨佔鎖;
- 獲取
Codition Queue
的firstWaiter
,將節點轉移至Sync Queue
中去。
singleAll 操作
private void doSignalAll(Node first) {
lastWaiter = firstWaiter = null;
do {
Node next = first.nextWaiter;
first.nextWaiter = null;
transferForSignal(first);
first = next;
} while (first != null);
}複製程式碼
signalAll
喚醒Condition Queue
的所有等待執行緒,將所有的Condition Queue
中的node
元素轉移至Sync Queue
中去。
其他API
這裡只介紹了獨佔鎖模式下,普通acquire
、release
方法的原理,AQS
還提供了很多可以供我們選擇的API
:
- 如優先考慮中斷、超時的:
acquireInterruptibly
、tryAcquireNanos
; - 如共享鎖模式下
acquireShared
、releaseShared
;
等等...
這裡暫時不詳細分析,後面有時間的話可以再做了解。
參考: