3.4 Template Method node
AbstractQueuedSynchronizer提供瞭如下幾個protected方法用於子類改寫 多線程
Java代碼
- protected boolean tryAcquire(int arg)
- protected boolean tryRelease(int arg)
- protected int tryAcquireShared(int arg)
- protected boolean tryReleaseShared(int arg)
- protected boolean isHeldExclusively()
這幾個方法的默認實現是拋出UnsupportedOperationException,子類能夠根據須要進行改寫。 併發
AbstractQueuedSynchronizer中最基本的acquire流程的相關代碼以下: app
Java代碼
- public final void acquire(int arg) {
- if (!tryAcquire(arg) &&
- acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
- selfInterrupt();
- }
-
- 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);
- }
- }
若是tryAcquire失敗,那麼當前線程可能會被enqueue到WaitQueue,而後被阻塞。 shouldParkAfterFailedAcquire方法會確保每一個線程在被阻塞以前,其對應WaitQueue中的節點的waitStatus被設置爲Node.SIGNAL(-1),以便在release時避免沒必要要的unpark操做。此外shouldParkAfterFailedAcquire還會清理WaitQueue中已經超時或者取消的Node。須要注意的是,在某個線程最終被阻塞以前,tryAcquire可能會被屢次調用。 ide
AbstractQueuedSynchronizer中最基本的release流程的相關代碼以下: 函數
Java代碼
- 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);
- }
release方法中,老是總head節點開始向後查找sucessor。只有當該sucessor的waitStatus被設置的狀況下才會調用unparkSuccessor。unparkSuccessor方法中首先清除以前設置的Node.waitStatus,而後向後查找而且喚醒第一個須要被喚醒的sucessor。須要注意的是,if (s == null || s.waitStatus > 0)這個分支中,查找是從tail節點開始,根據prev引用向前進行。在Inside AbstractQueuedSynchronizer (2) 中提到過,Node.next爲null並不必定意味着沒有sucessor,雖然WaitQueue是個雙向鏈表,可是根據next引用向後查找sucessor不靠譜,而根據prev引用向前查找predecessor老是靠譜。 ui
3.5 Fairness this
到目前爲止咱們已經知道,WaitQueue是個FIFO的隊列,喚醒也老是從head開始。可是AbstractQueuedSynchronizer卻並不必定是公平的(實際上,大多數狀況下都是在非公平模式下工做)。若是在看一遍acquire方法會發現,tryAcquire的調用順序先於acquireQueued,也就是說後來的線程可能在等待中的線程以前acquire成功。這種場景被稱爲barging FIFO strategy,它能提供更高的吞吐量。 spa
大多數AbstractQueuedSynchronizer的子類都同時提供了公平和非公平的實現,例如ReentrantLock提供了NonfairSync和FairSync。例如其FairSync的tryAcquire方法以下: .net
Java代碼
- protected final boolean tryAcquire(int acquires) {
- final Thread current = Thread.currentThread();
- int c = getState();
- if (c == 0) {
- if (!hasQueuedPredecessors() &&
- compareAndSetState(0, acquires)) {
- setExclusiveOwnerThread(current);
- return true;
- }
- }
- else if (current == getExclusiveOwnerThread()) {
- int nextc = c + acquires;
- if (nextc < 0)
- throw new Error("Maximum lock count exceeded");
- setState(nextc);
- return true;
- }
- return false;
- }
tryAcquire方法返回true的條件之一是!hasQueuedPredecessors() 。hasQueuedPredecessors的代碼以下:
Java代碼
- public final boolean hasQueuedPredecessors() {
- // The correctness of this depends on head being initialized
- // before tail and on head.next being accurate if the current
- // thread is first in queue.
- Node t = tail; // Read fields in reverse initialization order
- Node h = head;
- Node s;
- return h != t &&
- ((s = h.next) == null || s.thread != Thread.currentThread());
- }
綜上, FairSync優先確保等待中線程先acquire成功。可是公平性也不是絕對的:在一個多線程併發的環境下,就算鎖的獲取是公平的,也不保證後續的其它處理過程的前後順序。
既然默認狀況下使用的都是NonfairSync,那麼FairSync適合什麼樣的場景呢?若是被鎖所保護的代碼段的執行時間比較長,而應用又不能接受線程飢餓(NonfairSync可能會致使雖然某個線程長時間排隊,可是仍然沒法得到鎖的狀況)的場景下能夠考慮使用FairSync。對於ReentrantLock,在其構造函數中傳入true,便可構造一把公平鎖。