Java併發和多執行緒4:使用通用同步工具CountDownLatch實現執行緒等待

小雷FansUnion發表於2015-12-31
CountDownLatch,一個同步輔助類,在完成一組正在其他執行緒中執行的操作之前,它允許一個或多個執行緒一直等待。 


用給定的計數 初始化 CountDownLatch。由於呼叫了 countDown() 方法,所以在當前計數到達零之前,await 方法會一直受阻塞。
之後,會釋放所有等待的執行緒,await 的所有後續呼叫都將立即返回。
這種現象只出現一次——計數無法被重置。如果需要重置計數,請考慮使用 CyclicBarrier。 


CountDownLatch 是一個通用同步工具,它有很多用途。
將計數 1 初始化的 CountDownLatch 用作一個簡單的開/關鎖存器,或入口:在通過呼叫 countDown() 的執行緒開啟入口前,所有呼叫 await的執行緒都一直在入口處等待。
用 N 初始化的 CountDownLatch 可以使一個執行緒在 N 個執行緒完成某項操作之前一直等待,或者使其在某項操作完成 N 次之前一直等待。 


CountDownLatch 的一個有用特性是,它不要求呼叫 countDown 方法的執行緒等到計數到達零時才繼續,而在所有執行緒都能通過之前,它只是阻止任何執行緒繼續通過一個 await。 




用法1: 下面給出了兩個類,其中一組 worker 執行緒使用了兩個倒計數鎖存器: 
// 第一個類是一個啟動訊號,在 driver 為繼續執行 worker 做好準備之前,它會阻止所有的 worker 繼續執行。
// 第二個類是一個完成訊號,它允許 driver 在完成所有 worker 之前一直等待。
class Driver {
	void start() throws InterruptedException {
		CountDownLatch startSignal = new CountDownLatch(1);
		int N = 5;
		CountDownLatch doneSignal = new CountDownLatch(N);


		for (int i = 0; i < N; i++) {
			Worker worker = new Worker(startSignal, doneSignal,i);
			Thread thread = new Thread(worker);
			thread.start();
		}


		doSomethingElse(1); // 所有執行緒都還沒有開始執行
		startSignal.countDown(); // 讓所有執行緒開始執行
		doSomethingElse(2);
		doneSignal.await(); // 等待所有執行緒結束
		doSomethingElse(3);
	}


	// 執行一些其它的事情,具體結合實際情況
	private void doSomethingElse(int i) {
		System.out.println("doSomethingElse-"+i);
	}
}


class Worker implements Runnable {
	private final CountDownLatch startSignal;
	private final CountDownLatch doneSignal;
	private final int i;
	Worker(CountDownLatch startSignal, CountDownLatch doneSignal,int i) {
		this.startSignal = startSignal;
		this.doneSignal = doneSignal;
		this.i=i;
	}


	public void run() {
		try {
			startSignal.await();
			doWork(i);
			doneSignal.countDown();
		} catch (InterruptedException ex) {
			ex.printStackTrace();
		}
	}


	void doWork(int i) {
		System.out.println("doWork-"+i);
	}
}






// 另一種典型用法2,將一個問題分成 N 個部分,用執行每個部分並讓鎖存器倒計數的 Runnable 來描述每個部分,然後將所有 Runnable
// 加入到 Executor 佇列。當所有的子部分完成後,協調執行緒就能夠通過
// await。(當執行緒必須用這種方法反覆倒計數時,可改為使用CyclicBarrier。)


class Driver2 {
	void start() throws InterruptedException {
		int N = 5;
		CountDownLatch doneSignal = new CountDownLatch(N);
		ExecutorService e = Executors.newFixedThreadPool(3);
		// 建立並執行執行緒
		for (int i = 0; i < N; ++i) {
			WorkerRunnable workerRunnable = new WorkerRunnable(doneSignal, i);
			e.execute(workerRunnable);
		}
		// 等待所有執行緒結束
		doneSignal.await();
		//手動關閉,才會停止所有執行緒
		e.shutdown();
	}
}


class WorkerRunnable implements Runnable {
	private final CountDownLatch doneSignal;
	private final int i;


	WorkerRunnable(CountDownLatch doneSignal, int i) {
		this.doneSignal = doneSignal;
		this.i = i;
	}


	public void run() {
		doWork(i);
		doneSignal.countDown();
	}


	void doWork(int i) {
		System.out.println("doWork-" + i);
	}
}




執行程式
package cn.fansunion.executorframework;


import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;




public class CountDownLatchDemo {


	public static void main(String[] args) throws InterruptedException {
		//Driver中的若干Thread,執行完畢後,自動就關閉了
		Driver driver = new Driver();
		driver.start();
		//Driver2中,需要手動呼叫ExecutorService.shutdown關閉執行緒
		Driver2 driver2 = new Driver2();
		driver2.start();
	}


}




控制檯結果
doSomethingElse-1
doSomethingElse-2
doWork-0
doWork-2
doWork-4
doWork-3
doWork-1
doSomethingElse-3
doWork-1
doWork-0
doWork-3
doWork-4
doWork-2




更多程式碼示例:
http://git.oschina.net/fansunion/Concurrent(逐步更新中)


參考資料:


有條件地終止 ScheduledExecutorService 中執行的定時任務
http://www.oschina.net/question/1158769_119659?sort=time


JDK API 文件

相關文章