CountDownLatch是一個同步輔助類,在完成一組正在其他線程中執行的操作之前,它允許一個或多個線程一直等待。內部採用的公平鎖和共享鎖的機制實現。
一、CountDownLatch的構造函數(採用的是一種公平鎖機制)
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
二、CountDownLatch的函數列表
void await():如果當前count大於0,當前線程將會wait,直到count等於0或者中斷。PS:當count等於0的時候,再去調用await(),
線程將不會阻塞,而是立即運行。後面可以通過源碼分析得到。
boolean await(long timeout, TimeUnit unit):使當前線程在鎖存器倒計數至零之前一直等待,除非線程被中斷或超出了指定的等待時間。
void countDown(): 遞減鎖存器的計數,如果計數到達零,則釋放所有等待的線程。
long getCount() :獲得計數的數量
String toString() :沒什麼好說的
三、CountDownLatch的數據結構:
四、await()的分析
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
AQS:acquireSharedInterruptibly(int arg)
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())//判斷是否發生中斷
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)//注意:-1表示獲取到了共享鎖,1表示沒有獲取共享鎖
doAcquireSharedInterruptibly(arg);
}
CountDownLatch$Sync:int tryAcquireShared(int acquires)
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;//這裏的state就是最開始new CountDownLatch(int count),count等於state
}
如果獲取共享鎖繼續調用doAcquireSharedInterruptibly(arg)
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);//就判斷嘗試獲取鎖
/*
這裏要注意一下r的值就2種情況-1和1:
情況1.r爲-1,latch沒有調用countDown(),state是沒有變化的導致state一直大於0或者調用了countDown(),但是state不等於0,直接在for循環中等待
情況2.r爲1,證明countDown(),已經減到0,當前線程還在隊列中,state已經等於0了.接下來就是喚醒隊列中的節點
*/
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);
}
}
如果這裏多個線程wait之間沒有調用countDown(),線程都在等待。如下圖:
setHeadAndPropagate(Node node, int propagate)
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // 記錄頭結點
setHead(node);//設置node爲頭結點
/*
*從上面傳遞過來 propagate = 1;
*一定會進入下面的判斷
*/
if (propagate > 0 || h == null || h.waitStatus < 0) {
Node s = node.next;//獲得當前節點的下一個節點,如果爲最後一個節點或者,爲shared
if (s == null || s.isShared())
doReleaseShared();//釋放共享鎖
}
}
isShared()
final boolean isShared() {
return nextWaiter == SHARED;
}
釋放共享鎖,通知後面的節點。
private void doReleaseShared() {
for (;;) {
Node h = head;//獲得頭結點
if (h != null && h != tail) {
int ws = h.waitStatus;//獲取頭結點的狀態默認值爲0
if (ws == Node.SIGNAL) {如果等於SIGNAL喚醒狀態
//將頭結點的狀態置成0,並使用Node.SIGNAL(-1)與0比較,continue,h的狀態設置爲0,不會再進入if (ws == Node.SIGNAL)
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}//判斷ws是否爲0,並且h的狀態不等於0,這裏是個坑啊,ws等於0,h就是0啊,所以if進不來的,並設置節點爲PROPAGATE
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
如果頭結點等於h,其實也沒有一直loop,由於上面寫的Node h = head,就算前面的條件都不滿足,這裏一定會break
if (h == head) // loop if head changed
break;
}
}
compareAndSetWaitStatus(Node,int,int)
private static final boolean compareAndSetWaitStatus(Node node,
int expect,
int update) {
return unsafe.compareAndSwapInt(node, waitStatusOffset,
expect, update);//使用JNI調用c++代碼
}
看jvm的代碼 hotspot-9646293b9637\src\share\vm\runtime unsafe.cpp
c++:comapreAndSwapInt
UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
UnsafeWrapper("Unsafe_CompareAndSwapInt");
oop p = JNIHandles::resolve(obj);
//獲得對象地址指針
jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
//比較原來的狀態值是否和期待的狀態值一樣
return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END
atomic.cpp
c++:cmpxchg 根據(update參數來看)代碼應該賦值後在比較,但是while比較之後會再一次賦值。
jbyte Atomic::cmpxchg(jbyte exchange_value, volatile jbyte* dest, jbyte compare_value) {
assert(sizeof(jbyte) == 1, "assumption.");
uintptr_t dest_addr = (uintptr_t)dest;
uintptr_t offset = dest_addr % sizeof(jint);
volatile jint* dest_int = (volatile jint*)(dest_addr - offset);
jint cur = *dest_int;
//保存原來的值
jbyte* cur_as_bytes = (jbyte*)(&cur);
jint new_val = cur;
jbyte* new_val_as_bytes = (jbyte*)(&new_val);
//更新狀態值
new_val_as_bytes[offset] = exchange_value;
//原來的狀態置和比較的值進行比較
while (cur_as_bytes[offset] == compare_value) {
jint res = cmpxchg(new_val, dest_int, cur);
if (res == cur) break;
cur = res;
new_val = cur;
new_val_as_bytes[offset] = exchange_value;
}
//還回原來的狀態值
return cur_as_bytes[offset];
}
五、countDown()分析
public void countDown() {
sync.releaseShared(1);//每次釋放一個
}
boolean releaseShared(int arg)
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {//嘗試釋放共享鎖
doReleaseShared();//釋放共享鎖,上面有泛型不在重複
//由於state等於0,所以從隊列中,從頭結點一個接着一個退出(break),for循環等待,
return true;
}
return false;
}
tryReleaseShared(int)
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))//比較並設置state
return nextc == 0; //等於0的時候,肯定還有一些Node在隊列中,所以要調用doReleaseShared(),來清理
}
}