上篇博文(【java并發編程實戰】-----“J.U.C”:CyclicBarrier)LZ介紹了CyclicBarrier。CyclicBarrier所描述的是“允許一組線程互相等待,直到到達某個公共屏障點,才會進行后續任務”。而CountDownlatch和它也有一點點相似之處:CountDownlatch所描述的是“在完成一組正在其他線程中執行的操作之前,它允許一個或多個線程一直等待”。在JDK API中是這樣闡述的:
用給定的計數 初始化 CountDownLatch。由于調用了 countDown() 方法,所以在當前計數到達零之前,await 方法會一直受阻塞。之后,會釋放所有等待的線程,await 的所有后續調用都將立即返回。這種現象只出現一次——計數無法被重置。如果需要重置計數,請考慮使用 CyclicBarrier。
CountDownLatch 是一個通用同步工具,它有很多用途。將計數 1 初始化的 CountDownLatch 用作一個簡單的開/關鎖存器,或入口:在通過調用 countDown() 的線程打開入口前,所有調用 await 的線程都一直在入口處等待。用 N 初始化的 CountDownLatch 可以使一個線程在 N 個線程完成某項操作之前一直等待,或者使其在某項操作完成 N 次之前一直等待。
CountDownLatch 的一個有用特性是,它不要求調用 countDown 方法的線程等到計數到達零時才繼續,而在所有線程都能通過之前,它只是阻止任何線程繼續通過一個 await。
雖然,CountDownlatch與CyclicBarrier有那么點相似,但是他們還是存在一些區別的:
1、CountDownLatch的作用是允許1或N個線程等待其他線程完成執行;而CyclicBarrier則是允許N個線程相互等待。
2、 CountDownLatch的計數器無法被重置;CyclicBarrier的計數器可以被重置后使用,因此它被稱為是循環的barrier。
CountDownLatch結構如下:

從上圖中可以看出CountDownLatch依賴Sync,其實CountDownLatch內部采用的是共享鎖來實現的(內部Sync的實現可以看出)。它的構造函數如下:
CountDownLatch(int count):構造一個用給定計數初始化的 CountDownLatch。
public CountDownLatch(int count) { if (count < 0) throw new IllegalArgumentException("count < 0"); this.sync = new Sync(count); }
以下源代碼可以證明,CountDownLatch內部是采用共享鎖來實現的:
PRivate static final class Sync extends AbstractQueuedSynchronizer { private static final long serialVersionUID = 4982264981922014374L; protected int tryAcquireShared(int acquires) { /** 省略源代碼 **/ } protected boolean tryReleaseShared(int releases) { /** 省略源代碼 **/ } }
CountDownLatch提供了await方法來實現:
await():使當前線程在鎖存器倒計數至零之前一直等待,除非線程被中斷。
await(long timeout, TimeUnit unit): 使當前線程在鎖存器倒計數至零之前一直等待,除非線程被中斷或超出了指定的等待時間。
public void await() throws InterruptedException { sync.acquireSharedInterruptibly(1); }
await內部調用sync的acquireSharedInterruptibly方法:
public final void acquireSharedInterruptibly(int arg) throws InterruptedException { //線程中斷,拋出InterruptedException異常 if (Thread.interrupted()) throw new InterruptedException(); if (tryAcquireShared(arg) < 0) doAcquireSharedInterruptibly(arg); }
acquireSharedInterruptibly()的作用是獲取共享鎖。如果在獲取共享鎖過程中線程中斷則拋出InterruptedException異常。否則通過tryAcquireShared方法來嘗試獲取共享鎖。如果成功直接返回,否則調用doAcquireSharedInterruptibly方法。
tryAcquireShared源碼:
protected int tryAcquireShared(int acquires) { return (getState() == 0) ? 1 : -1; }
tryAcquireShared方法被CountDownLatch重寫,他的主要作用是嘗試著獲取鎖。getState == 0 表示鎖處于可獲取狀態返回1否則返回-1;當tryAcquireShared返回-1獲取鎖失敗,調用doAcquireSharedInterruptibly獲取鎖:
private void doAcquireSharedInterruptibly(int arg) throws InterruptedException { //創建當前線程(共享鎖)Node節點 final Node node = addWaiter(Node.SHARED); boolean failed = true; try { for (;;) { //獲取當前節點的前繼節點 final Node p = node.predecessor(); //如果當前節點為CLH列頭,則嘗試獲取鎖 if (p == head) { //獲取鎖 int r = tryAcquireShared(arg); if (r >= 0) { setHeadAndPropagate(node, r); p.next = null; // help GC failed = false; return; } } //如果當前節點不是CLH列頭,當前線程一直等待,直到獲取鎖為止 if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) throw new InterruptedException(); } } finally { if (failed) cancelAcquire(node); } }
該方法當中的方法,前面博客都講述過,請參考:【Java并發編程實戰】-----“J.U.C”:ReentrantLock之二lock方法分析、【Java并發編程實戰】-----“J.U.C”:Semaphore。
CountDownLatch,除了提供await方法外,還提供了countDown(),countDown所描述的是“遞減鎖存器的計數,如果計數到達零,則釋放所有等待的線程。”,源碼如下:
public void countDown() { sync.releaseShared(1); }
countDown內部調用releaseShared方法來釋放線程:
public final boolean releaseShared(int arg) { //嘗試釋放線程,如果釋放釋放則調用doReleaseShared() if (tryReleaseShared(arg)) { doReleaseShared(); return true; } return false; }
tryReleaseShared,同時被CountDownLatch重寫了:
protected boolean tryReleaseShared(int releases) { for (;;) { //獲取鎖狀態 int c = getState(); //c == 0 直接返回,釋放鎖成功 if (c == 0) return false; //計算新“鎖計數器” int nextc = c-1; //更新鎖狀態(計數器) if (compareAndSetState(c, nextc)) return nextc == 0; } }
總結:
CountDownLatch內部通過“共享鎖”實現。在創建CountDownLatch時,需要傳遞一個int類型的count參數,該count參數為“鎖狀態”的初始值,該值表示著該“共享鎖”可以同時被多少線程獲取。當某個線程調用await方法時,首先判斷鎖的狀態是否處于可獲取狀態(其條件就是count==0?),如果共享鎖可獲取則獲取共享鎖,否則一直處于等待直到獲取為止。當線程調用countDown方法時,計數器count – 1。當在創建CountDownLatch時初始化的count參數,必須要有count線程調用countDown方法才會使計數器count等于0,鎖才會釋放,前面等待的線程才會繼續運行。
員工開會只有當所有人到期之后才會開戶。我們初始化與會人員為3個,那么CountDownLatch的count應為3:
public class Conference implements Runnable{ private final CountDownLatch countDown; public Conference(int count){ countDown = new CountDownLatch(count); } /** * 與會人員到達,調用arrive方法,到達一個CountDownLatch調用countDown方法,鎖計數器-1 * @author:chenssy * @data:2015年9月6日 * * @param name */ public void arrive(String name){ System.out.println(name + "到達....."); //調用countDown()鎖計數器 - 1 countDown.countDown(); System.out.println("還有 " + countDown.getCount() + "沒有到達..."); } @Override public void run() { System.out.println("準備開會,參加會議人員總數為:" + countDown.getCount()); //調用await()等待所有的與會人員到達 try { countDown.await(); } catch (InterruptedException e) { } System.out.println("所有人員已經到達,會議開始....."); }}
參加與會人員Participater:
public class Participater implements Runnable{ private String name; private Conference conference; public Participater(String name,Conference conference){ this.name = name; this.conference = conference; } @Override public void run() { conference.arrive(name); }}
Test:
public class Test { public static void main(String[] args) { //啟動會議室線程,等待與會人員參加會議 Conference conference = new Conference(3); new Thread(conference).start(); for(int i = 0 ; i < 3 ; i++){ Participater participater = new Participater("chenssy-0" + i , conference); Thread thread = new Thread(participater); thread.start(); } }}
運行結果:
準備開會,參加會議人員總數為:3chenssy-01到達.....還有 2沒有到達...chenssy-00到達.....還有 1沒有到達...chenssy-02到達.....還有 0沒有到達...所有人員已經到達,會議開始.....
新聞熱點
疑難解答