CountDownLatch是通過一個計數器來實現的,當我們在new 一個CountDownLatch對象的時候需要帶入該計數器值,該值就表示了線程的數量。下面我們來深入瞭解一下吧
CountDownLatch簡介
CountDownLatch顧名思義,count + down + latch = 計數 + 減 + 門閂(這麼拆分也是便於記憶=_=) 可以理解這個東西就是個計數器,只能減不能加,同時它還有個門閂的作用,當計數器不爲0時,門閂是鎖着的;當計數器減到0時,門閂就打開了。
如果你感到懵比的話,可以類比考生考試交卷,考生交一份試卷,計數器就減一。直到考生都交了試卷(計數器爲0),監考老師(一個或多個)才能離開考場。至於考生是否做完試卷,監考老師並不關注。只要都交了試卷,他就可以做接下來的工作了。
CountDownLatch實現原理
下面從構造方法開始,一步步解釋實現的原理:構造方法下面是實現的源碼,非常簡短,主要是創建了一個Sync對象。
public CountDownLatch(int count) { if (count < 0) throw new IllegalArgumentException("count < 0"); this.sync = new Sync(count); }
Sync對象
private static final class Sync extends AbstractQueuedSynchronizer { private static final long serialVersionUID = 4982264981922014374L; Sync(int count) { setState(count); } int getCount() { return getState(); } protected int tryAcquireShared(int acquires) { return (getState() == 0) ? 1 : -1; } 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; } } }
假設我們是這樣創建的:new CountDownLatch(5)。其實也就相當於new Sync(5),相當於setState(5)。setState其實就是共享鎖資源總數,我們可以暫時理解爲設置一個計數器,當前計數器初始值爲5。
tryAcquireShared方法其實就是判斷一下當前計數器的值,是否爲0了,如果爲0的話返回1(返回1的時候,就表示獲取鎖成功,awit()方法就不再阻塞)。
tryReleaseShared方法就是利用CAS的方式,對計數器進行減一的操作,而我們實際上每次調用countDownLatch.countDown()方法的時候,最終都會調到這個方法,對計數器進行減一操作,一直減到0爲止。
countDownLatch.await()
public void await() throws InterruptedException { sync.acquireSharedInterruptibly(1); }
代碼很簡單,就一句話(注意acquireSharedInterruptibly()方法是抽象類:AbstractQueuedSynchronizer的一個方法,我們上面提到的Sync繼承了它),我們跟蹤源碼,繼續往下看:
acquireSharedInterruptibly(int arg) public final void acquireSharedInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); if (tryAcquireShared(arg) < 0) doAcquireSharedInterruptibly(arg); }
源碼也是非常簡單的,首先判斷了一下,當前線程是否有被中斷,如果沒有的話,就調用tryAcquireShared(int acquires)方法,判斷一下當前線程是否還需要“阻塞”。其實這裏調用的tryAcquireShared方法,就是我們上面提到的java.util.concurrent.CountDownLatch.Sync.tryAcquireShared(int)這個方法。
當然,在一開始我們沒有調用過countDownLatch.countDown()方法時,這裏tryAcquireShared方法肯定是會返回-1的,因爲會進入到doAcquireSharedInterruptibly方法。
doAcquireSharedInterruptibly(int arg)
countDown()方法
// 計數器減1 public void countDown() { sync.releaseShared(1); } //調用AQS的releaseShared方法 public final boolean releaseShared(int arg) { if (tryReleaseShared(arg)) {//計數器減一 doReleaseShared();//喚醒後繼結點,這個時候隊列中可能只有調用過await()的線程節點,也可能隊列爲空 return true; } return false; }
這個時候,我們應該對於countDownLatch.await()方法是怎麼“阻塞”當前線程的,已經非常明白了。其實說白了,就是當你調用了countDownLatch.await()方法後,你當前線程就會進入了一個死循環當中,在這個死循環裏面,會不斷的進行判斷,通過調用tryAcquireShared方法,不斷判斷我們上面說的那個計數器,看看它的值是否爲0了(爲0的時候,其實就是我們調用了足夠多 countDownLatch.countDown()方法的時候),如果是爲0的話,tryAcquireShared就會返回1,代碼也會進入到圖中的紅框部分,然後跳出了循環,也就不再“阻塞”當前線程了。
需要注意的是,說是在不停的循環,其實也並非在不停的執行for循環裏面的內容,因爲在後面調用parkAndCheckInterrupt()方法時,在這個方法裏面是會調用 LockSupport.park(this);來掛起當前線程。
CountDownLatch 使用的注意點:
1、只有當count爲0時,await之後的程序纔夠執行。
2、countDown必須寫在finally中,防止發生異程常時,導致程序死鎖。
使用場景:
比如對於馬拉松比賽,進行排名計算,參賽者的排名,肯定是跑完比賽之後,進行計算得出的,翻譯成Java識別的預發,就是N個線程執行操作,主線程等到N個子線程執行完畢之後,在繼續往下執行。
public static void testCountDownLatch(){ int threadCount = 10; final CountDownLatch latch = new CountDownLatch(threadCount); for(int i=0; i< threadCount; i++){ new Thread(new Runnable() { @Override public void run() { System.out.println("線程" + Thread.currentThread().getId() + "開始出發"); try { Thread.sleep(1000); System.out.println("線程" + Thread.currentThread().getId() + "已到達終點"); } catch (InterruptedException e) { e.printStackTrace(); } fianlly { latch.countDown(); } } }).start(); } try { latch.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("10個線程已經執行完畢!開始計算排名"); }
結果:
線程10開始出發 線程13開始出發 線程12開始出發 線程11開始出發 線程14開始出發 線程15開始出發 線程16開始出發 線程17開始出發 線程18開始出發 線程19開始出發 線程14已到達終點 線程15已到達終點 線程13已到達終點 線程12已到達終點 線程10已到達終點 線程11已到達終點 線程16已到達終點 線程17已到達終點 線程18已到達終點 線程19已到達終點 10個線程已經執行完畢!開始計算排名
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持神馬文庫。