功能描述
一個同步輔助類,在完成一組正在其他線程中執行的操作之前,它允許一個或多個線程一直等待。
常見用法
多個人等一個信號後繼續執行操作。例如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就是一個不斷釋放鎖的過程。
常見問題
- CountDownLatch是不能夠重用的
根據上面的解析,大家也發現,共享鎖加鎖的操作並不會增加state的值。CountDownLatch中state一旦變成0就沒有提供其他方式增長回去了。
所以CountDownLatch是一次性消耗品,用完就得換新的。這樣設計也是比較正常的,對狀態的要求比較嚴格,例如都開車了你再告訴司機,這次加了5個人,車都開了一半了,不會考慮再回去。隨意修改約束值會帶來很多邏輯上的問題。
- CountDownLatch無限等待
countDown沒有被調用,那麼await就會一直等下去。countDown常見沒有被調用的情況:異常中斷,線程池拒絕策略。
可以使用
public boolean await(long timeout, TimeUnit unit)
根據業務情況,增加一個最大等待時間。使用這種方式,需要對失敗的各種情況作出業務上的對應處理,否則就出現各種數據不正確的問題。也可以對countDown的線程做好異常處理,最好使用另外一個線程池來處理這些線程。這種情況就需要對業務不能停頓時間特別長,導致線程池的資源被耗光的情況做處理。如果是想通過new Thread避免,就需要考慮線程突然暴漲的問題。