在過去我們實現多執行緒同步的程式碼中,往往使用join()、wait()、notiyAll()等執行緒間通訊的方式,隨著JUC包的不斷的完善,java為我們提供了豐富同步工具類,官方也鼓勵我們使用工具類來實現多執行緒的同步,今天我們就對其中CountDownLatch類的使用與底層實現進行分析與總結。
一、CountDownLatch使用
CountDownLatch其實可以看做一個計數器,統計多個執行緒執行完成的情況,適用於控制一個或多個執行緒等待,直到所有執行緒都執行完畢的場景,類似與Thread.join()的作用。下面我們通過一個簡單的例子看下CountDownLatch的使用。
public static void main(String[] args) { final CountDownLatch countDownLatch = new CountDownLatch(5); // 啟動計數執行緒 for (int i = 0; i < 5; i++) { new CountDownLatchThread(i, countDownLatch).start(); } // 啟動等待執行緒 for (int i = 0; i < 5; i++) { new Thread() { public void run() { try { countDownLatch.await(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("計數完畢了," + Thread.currentThread().getName() + "等待執行緒執行"); } }.start(); } }
計數執行緒程式碼:
public class CountDownLatchThread extends Thread { private CountDownLatch countDownLatch; private int name; private int count; public CountDownLatchThread(int name, CountDownLatch countDownLatch) { this.name = name; this.countDownLatch = countDownLatch; this.count = 0; } public void run() { try { for (int i = 0; i < 10; i++) { Thread.sleep(100); count++; } System.out.println(name + "號執行緒--" + Thread.currentThread().getName() + "--計數完成了"); countDownLatch.countDown(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
輸出結果:
1號執行緒--Thread-1--計數完成了 0號執行緒--Thread-0--計數完成了 4號執行緒--Thread-4--計數完成了 2號執行緒--Thread-2--計數完成了 3號執行緒--Thread-3--計數完成了 計數完畢了,Thread-5等待執行緒執行 計數完畢了,Thread-6等待執行緒執行 計數完畢了,Thread-7等待執行緒執行 計數完畢了,Thread-8等待執行緒執行 計數完畢了,Thread-9等待執行緒執行
通過上面的例子可以看到,利用CountDownLatch的countDown方法與await()方法,我們可以同步計數執行緒與等待執行緒,使等待執行緒在所有計數執行緒完成後再開始執行。
二、CountDownLatch原始碼分析
接下來我們對countDownLatch內部原始碼進行一下分析。
1、CountDownLatch的構造。
首先看下CountDownLatch的建構函式
public CountDownLatch(int count) { if (count < 0) throw new IllegalArgumentException("count < 0"); this.sync = new Sync(count); }
CountDownLatch的建構函式會接收一個count值做為計數器,也就是如果你需要等待N個執行緒執行結束,那這裡就傳入N。同時CountDownLatch會例項化一個Sync物件,這個Sync其實是CountDownLatch內部定義的一個繼承自AbstractQueuedSynchronizer的實現類,所以CountDownLatch提供的同步和其他功能都是圍繞Sync這個子類實現的,也就是基於AbstractQueuedSynchronizer類來實現的。
我們來看下Sync這個類的定義
private static final class Sync extends AbstractQueuedSynchronizer { private static final long serialVersionUID = 4982264981922014374L; /** 1、設定AbstractQueuedSynchronizer中同步狀態的值state,也就是計數器的值。 2、這個值volatile變數,必須保證執行緒間的可見性; **/ Sync(int count) { setState(count); } int getCount() { return getState(); } //獲取同步狀態的值 protected int tryAcquireShared(int acquires) { return (getState() == 0) ? 1 : -1; } protected boolean tryReleaseShared(int releases) { // 通過CAS操作改變同步狀態值,保證同步狀態的值state的執行緒安全。 for (;;) { int c = getState(); if (c == 0) return false; int nextc = c-1; if (compareAndSetState(c, nextc)) return nextc == 0; } } }
2、countDown方法
首先我們看下countDown方法的原始碼
public void countDown() { //改變同步狀態值,執行緒執行完成時計數器減一 sync.releaseShared(1); }
AbstractQueuedSynchronizer類中releaseShared() 方法的原始碼
public final boolean releaseShared(int arg) { // CountDownLatch定義的子類Sync實現,通過CAS操作改變State的值 if (tryReleaseShared(arg)) { //State以遞減為0,代表著所有執行執行緒執行完畢,共享模式下釋放鎖,那麼等待執行緒就能夠拿到鎖往下執行。 doReleaseShared(); return true; } return false; }
當呼叫CountDownLatch的countDown方法時,就會執行計數器進行減一操作,直到所有執行緒全部執行完畢,計算器為0時喚醒等待執行緒。
AbstractQueuedSynchronizer中doReleaseShared方法是執行共享模式下釋放鎖的操作,從而讓等待執行緒獲取鎖,繼續向下執行。
3、await方法
public void await() throws InterruptedException { sync.acquireSharedInterruptibly(1); }
AbstractQueuedSynchronizer類中acquireSharedInterruptibly() 方法的原始碼
public final void acquireSharedInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); //獲取同步狀態值 if (tryAcquireShared(arg) < 0) //同步狀態值即計數器的值不為0,等待執行緒共享模式下嘗試獲取鎖,獲取不到鎖的話進入阻塞 doAcquireSharedInterruptibly(arg); }
await方法的實現也很明確,首頁獲取同步狀態也就是計數器的值,如果為0即所有執行緒執行完畢返回1,否則返回-1的話,等待執行緒在共享模式下嘗試獲取鎖,獲取不到鎖的話進入阻塞;
AbstractQueuedSynchronizer中doAcquireSharedInterruptibly方法是執行共享模式下獲取鎖的操作;
三、總結
通過上面分析可以看到CountDownLatch是基於AbstractQueuedSynchronizer類實現的,一個非常實用的多執行緒控制工具類,它類似與一個計數器用來控制指定的執行緒等待,直到計數器歸零。以上我們對CountDownLatch類的使用與核心方法的原始碼進入了一定的分析,其中如有不足與不正確的地方還望指出與海涵。
關注微信公眾號,檢視更多技術文章。