CountDownLatch詳解

功能描述

一個同步輔助類,在完成一組正在其他線程中執行的操作之前,它允許一個或多個線程一直等待。
常見用法
多個人等一個信號後繼續執行操作。例如5個運動員,等一個發令員的槍響。
一個人等多個人的信號。旅遊團等所有人簽到完成纔開始出發。
我們最常見見到使用的地方是zk獲取連接的時候

       final CountDownLatch countDownLatch=new CountDownLatch(1); 
       ZooKeeper zooKeeper=new ZooKeeper(zk_url, time_out, new Watcher() { 
           public void process(WatchedEvent watchedEvent) { 
               Event.KeeperState state = watchedEvent.getState(); 
               Event.EventType type = watchedEvent.getType(); 
               if(Event.KeeperState.SyncConnected==state){ 
                   if(Event.EventType.None==type){ 
                       //調用此方法測計數減一 
                       countDownLatch.countDown(); 
                   } 
               } 
           } 
       }); 
       //阻礙當前線程進行,除非計數歸零 
       countDownLatch.await();

這裏了也可以看到很明確的使用方法,countDown直到0,await的線程纔會繼續執行。

原理

CountDownLatch內部使用了共享鎖。如果這裏還不知道共享和獨佔的區別,可以看前面的aqs速讀。
獲取鎖成功的方法很簡單

        protected int tryAcquireShared(int acquires) {
            return (getState() == 0) ? 1 : -1;
        }

共享鎖有個約定,返回有三種情況。

  • 0爲獲取鎖且沒有其他資源
  • 正數 獲取鎖並且還有其他資源
  • 負數 獲取鎖資源失敗

共享鎖在tryAcquireShared返回大於0的值的時候,會喚醒其他停頓狀態加鎖線程。由於沒有對state的增加操作,所以當state變成0的時候,所有嘗試加鎖的線程都會被喚醒。

        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;
            }
        }
        

釋放鎖的操作,就是把state的值減一,當只有state變成0的時候,才返回true,tryReleaseShared返回true的時候會觸發喚醒其他加鎖線程的操作。
通過上面的過程,我們可以看到CountDownLatch中的共享鎖的加鎖和釋放鎖的過程,下面看看是如何和CountDownLatch結合的。

    public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }

CountDownLatch的構造裏會初始化共享鎖,並且設置state的值。

    public void countDown() {
        sync.releaseShared(1);
    }

countDown是釋放鎖,最終會調用到tryReleaseShared。

    public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

await是加鎖,最終會調用到 tryAcquireShared。
CountDownLatch就是一個不斷釋放鎖的過程。

常見問題

  1. CountDownLatch是不能夠重用的
    根據上面的解析,大家也發現,共享鎖加鎖的操作並不會增加state的值。CountDownLatch中state一旦變成0就沒有提供其他方式增長回去了。

所以CountDownLatch是一次性消耗品,用完就得換新的。這樣設計也是比較正常的,對狀態的要求比較嚴格,例如都開車了你再告訴司機,這次加了5個人,車都開了一半了,不會考慮再回去。隨意修改約束值會帶來很多邏輯上的問題。

  1. CountDownLatch無限等待
    countDown沒有被調用,那麼await就會一直等下去。countDown常見沒有被調用的情況:異常中斷,線程池拒絕策略。

可以使用

public boolean await(long timeout, TimeUnit unit)

根據業務情況,增加一個最大等待時間。使用這種方式,需要對失敗的各種情況作出業務上的對應處理,否則就出現各種數據不正確的問題。也可以對countDown的線程做好異常處理,最好使用另外一個線程池來處理這些線程。這種情況就需要對業務不能停頓時間特別長,導致線程池的資源被耗光的情況做處理。如果是想通過new Thread避免,就需要考慮線程突然暴漲的問題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章