1. 程式人生 > >併發:等待多執行緒完成的CountDownLatch(倒數計時器)。

併發:等待多執行緒完成的CountDownLatch(倒數計時器)。

CountDownLatch允許一個或多個執行緒等待其他執行緒完成操作。

假如有這樣一個需求:我們需要解析一個Excel裡多個sheet的資料,此時可以考慮使用多執行緒,每個執行緒解析一個sheet裡的資料,等到所有的sheet都解析完之後,程式需要提示解析完成。在這個需求中,要實現主執行緒等待所有執行緒完成sheet的解析操作,最簡單的做法是使用join()方法,如下所示。

public class JoinCountDownLatchTest {
	public static void main(String[] args) throws InterruptedException {
		Thread parser1 = new Thread(new Runnable() {
			@Override
			public void run() {
				System.out.println("parser1 finish");
			}
		});
		Thread parser2 = new Thread(new Runnable() {
			@Override
			public void run() {
				System.out.println("parser2 finish");
			}
		});
		parser1.start();
		parser2.start();
		parser1.join();
		parser2.join();
		System.out.println("all parser finish");
	}
}

join用於讓當前執行執行緒等待join執行緒執行結束。其實現原理是不停檢查join執行緒是否存活,如果join執行緒存活則讓當前執行緒永遠等待。其中,wait(0)表示永遠等待下去,程式碼片段如下所示。

while(isAlive()) {
    wait(0);
}

直到join執行緒中止後,執行緒的this.notifyAll()方法會被呼叫,呼叫notifyAll()方法是在JVM裡實現的,所以在JDK裡看不到,大家可以檢視JVM原始碼。

在JDK 1.5之後的併發包中提供的CountDownLatch也可以實現join的功能,並且比join的功能更多,如下所示。

public class CountDownLatchTest {
	static CountDownLatch c = new CountDownLatch(2);
	public static void main(String[] args) throws InterruptedException {
		new Thread(new Runnable() {
			@Override
			public void run() {
				System.out.println(1);
				c.countDown();
				System.out.println(2);
				c.countDown();
			}
		}).start();
		c.await();
		System.out.println("3");
	}
}

CountDownLatch的建構函式接收一個int型別的引數作為計數器,如果你想等待N個點完成,這裡就傳入N。

當我們呼叫CountDownLatch的countDown方法時,N就會減1,CountDownLatch的await方法會阻塞當前執行緒,直到N變成零。由於countDown方法可以用在任何地方,所以這裡說的N個點,可以是N個執行緒,也可以是1個執行緒裡的N個執行步驟。用在多個執行緒時,只需要把這個CountDownLatch的引用傳遞到執行緒裡即可。

如果有某個解析sheet的執行緒處理的比較慢,我們不可能讓主執行緒一直等待,所以可以使用另外一個帶指定時間的await方法——await(long time, TimeUnit unit),這個方法等待特定時間後,就會不再阻塞當前執行緒。join也有類似的方法。

注意:計數器必須大於等於0,只是等於0時候,計數器就是零,呼叫await方法時不會阻塞當前執行緒。CountDownLatch不可能重新初始化或者修改CountDownLatch物件的內部計數器的值。一個執行緒呼叫countDown方法happen-before,另外一個執行緒呼叫await方法。