CountDownLatch源碼閱讀
await方法如何實現線程等待
await
方法,由CountDownLatch.Sync.acquireSharedInterruptibly
代理完成,實際上由Sync的父類AbstractQueuedSynchronizer
實現了該方法
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
由於arg參數入參固定爲1, 而Sync實現的tryAcquireShared
方法對於入參數1返回-1,因此實際相當於調用瞭如下代碼,Thread.interrupted
確保了當前線程不是interrupted的狀態
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
doAcquireSharedInterruptibly(arg);
}
AbstractQueuedSynchronizer
類的doAcquireSharedInterruptibly
方法,其中addWaiter
方法是往Node鏈表最後增加一個Node節點並返回該節點,Node對象中存儲了當前線程t
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
實際上由於arg值固定爲1,上面方法相當於如下
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
shouldParkAfterFailedAcquire
方法判斷當前新增的節點(node)的前置節點(p)是否持有信號,並在沒有持有信號的情況下,使其變更爲持有信號狀態(使state值設爲SIGNAL)。doAcquireSharedInterruptibly
中for循環直到shouldParkAfterFailedAcquire
方法判斷node的前置節點持有信號時,纔會調用parkAndCheckInterrupt
方法。
parkAndCheckInterrupt
代碼便是阻塞代碼真正所在的位置,該功方法代碼很簡單,直接委託LockSupport.park(this)
完成,LockSupport.park(this)
代碼如下
public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(false, 0L);
setBlocker(t, null);
}
setBlocker
時native的方法,它的作用是將第二個參數存儲在第一個參數(線程)的堆內存中(即這是一個直接操作堆內存存儲的實現),上面park
方法中UNSAFE.park
爲阻塞功能的實現,點進去看發現也是一個native修飾的方法,即真正阻塞功能的還是由底層實現的,沒法看到具體的代碼。
countDown方法如何實現計數遞減並取消阻塞
與await
方法一樣,countDown
方法也是將工作委託給了Sync類的方法完成。CountDownLatch.Sync.releaseShared
代理完成該功能,CountDownLatch.Sync.releaseShared
實際上也是已經由Sync的父類AbstractQueuedSynchronizer
實現了,該方法如下
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
tryReleaseShared
方法嘗試進行計數(即state
)遞減,tryReleaseShared
方法中與前面一些方法一樣,使用了原子性的方法compareAndSetState
完成遞減操作,該原子性操作保證了不會出現兩次countDown之後state
只遞減一次的情況。當本次遞減後計數達到0返回true,原先計數已經爲0或者本次遞減後計數不爲0,返回false。tryReleaseShared
返回true後,才能調用doReleaseShared
完成Node恢復阻塞的線程。
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
如下所示爲doReleaseShared
方法代碼,當ws == Node.SIGNAL
(持有信號)時,調用compareAndSetWaitStatus
嘗試使Node不再持有信號(state值從SIGNAL變爲0,compareAndSetWaitStatus
調用的unsafe.compareAndSwapInt
方法是原子性的方法),釋放信號成功則進行unparkSuccessor
的調用,不成功則繼續循環(一般另外一條線程同時操作這個node的時候纔會導致不成功),當ws == 0
時,將該Node標識轉爲PROPAGATE後不再作處理,這種情況基本與CountDownLatch
的業務無關,只是AbstractQueuedSynchronizer
對Node鏈表結構的維護工作
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
最後unparkSuccessor
方法,找到head Node的下一個Node s,找到阻塞的線程s.thread
, 調用LockSupport.unpark(s.thread)
解除線程阻塞(核心代碼UNSAFE.unpark(thread)
方法,是native方法,發現一點有意思的情況:該方法運行完會改變head的指向,雖然與這裏研究的關係不大,但傳入參數是thread卻能改變Sync的head Node指針確是挺有意思的)
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
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);
}
總結
CountDownLatch基本由CountDownLatch.Sync
代理,Sync大量調用了sun.misc.Unsafe
的代碼。使用Unsafe的用於改變變量值的原子性方法,減少一些鎖的使用;另外一點是使用park和sun.misc.Unsaft.park
和sun.misc.Unsaft.unpark
方法來實現指定線程的阻塞與解除阻塞。可惜的是sun.misc.Unsafe
類oracle並不開放給外部代碼使用。