Inside AbstractQueuedSynchronizer (1)
Inside AbstractQueuedSynchronizer (2)
3.4 Template Method
AbstractQueuedSynchronizer提供了以下幾個protected方法用于子類改寫
protected boolean tryAcquire(int arg) protected boolean tryRelease(int arg) protected int tryAcquireShared(int arg) protected boolean tryReleaseShared(int arg) protected boolean isHeldExclusively()
這幾個方法的默認(rèn)實現(xiàn)是拋出UnsupportedOperationException,子類可以根據(jù)需要進(jìn)行改寫。
AbstractQueuedSynchronizer中最基本的acquire流程的相關(guān)代碼如下:
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失敗,那么當(dāng)前線程可能會被enqueue到WaitQueue,然后被阻塞。 shouldParkAfterFailedAcquire方法會確保每個線程在被阻塞之前,其對應(yīng)WaitQueue中的節(jié)點的waitStatus被設(shè)置為Node.SIGNAL(-1),以便在release時避免不必要的unpark操作。此外shouldParkAfterFailedAcquire還會清理WaitQueue中已經(jīng)超時或者取消的Node。需要注意的是,在某個線程最終被阻塞之前,tryAcquire可能會被多次調(diào)用。
AbstractQueuedSynchronizer中最基本的release流程的相關(guān)代碼如下:
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節(jié)點開始向后查找sucessor。只有當(dāng)該sucessor的waitStatus被設(shè)置的情況下才會調(diào)用unparkSuccessor。unparkSuccessor方法中首先清除之前設(shè)置的Node.waitStatus,然后向后查找并且喚醒第一個需要被喚醒的sucessor。需要注意的是,if (s == null || s.waitStatus > 0)這個分支中,查找是從tail節(jié)點開始,根據(jù)prev引用向前進(jìn)行。在Inside AbstractQueuedSynchronizer (2) 中提到過,Node.next為null并不一定意味著沒有sucessor,雖然WaitQueue是個雙向鏈表,但是根據(jù)next引用向后查找sucessor不靠譜,而根據(jù)prev引用向前查找predecessor總是靠譜。
3.5 Fairness
到目前為止我們已經(jīng)知道,WaitQueue是個FIFO的隊列,喚醒也總是從head開始。但是AbstractQueuedSynchronizer卻并不一定是公平的(實際上,大多數(shù)情況下都是在非公平模式下工作)。如果在看一遍acquire方法會發(fā)現(xiàn),tryAcquire的調(diào)用順序先于acquireQueued,也就是說后來的線程可能在等待中的線程之前acquire成功。這種場景被稱為barging FIFO strategy,它能提供更高的吞吐量。
大多數(shù)AbstractQueuedSynchronizer的子類都同時提供了公平和非公平的實現(xiàn),例如ReentrantLock提供了NonfairSync和FairSync。例如其FairSync的tryAcquire方法如下:
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的代碼如下:
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優(yōu)先確保等待中線程先acquire成功。但是公平性也不是絕對的:在一個多線程并發(fā)的環(huán)境下,就算鎖的獲取是公平的,也不保證后續(xù)的其它處理過程的先后順序。
既然默認(rèn)情況下使用的都是NonfairSync,那么FairSync適合什么樣的場景呢?如果被鎖所保護(hù)的代碼段的執(zhí)行時間比較長,而應(yīng)用又不能接受線程饑餓(NonfairSync可能會導(dǎo)致雖然某個線程長時間排隊,但是仍然無法獲得鎖的情況)的場景下可以考慮使用FairSync。對于ReentrantLock,在其構(gòu)造函數(shù)中傳入true,即可構(gòu)造一把公平鎖。