Java併發之CountDownLatch源碼分析


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(),來清理
            }
        }



發佈了298 篇原創文章 · 獲贊 9 · 訪問量 24萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章