Java併發程式設計系列之二十一:CountdownLatch
CountDownLatch是JDK提供的併發工具包,理解並掌握這些工具包的使用有助於簡化特定場景下的程式設計。就CountDownLatch而言,允許一個或者多個執行緒等待其他執行緒完成操作。等待其他執行緒完成不是與Thread.join()方法類似嗎,因為Thread.join()就是讓當前的執行緒等待join的執行緒執行完畢再繼續執行。這裡基於一個簡單的需求實現CountDownLatch的功能:讀取某目錄下不同的檔案內容,每個執行緒讀取不同的檔案,等所有的執行緒都讀取完畢提示讀取完畢的資訊。
下面的程式碼使用Thread.join方法模擬了這個過程:
package com.rhwayfun.concurrency.r0406;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* Created by rhwayfun on 16-4-6.
*/
public class WaitJoinTaskDemo {
public static void main(String[] args) throws InterruptedException {
final DateFormat format = new SimpleDateFormat("HH:mm:ss");
//第一個執行緒開始讀取
Thread thread1 = new Thread(new Runnable() {
public void run() {
long start = System.currentTimeMillis();
//模擬IO的耗時過程
for (;;){
if (System.currentTimeMillis() - start > 1000 * 10){
break;
}
}
System.out.println(Thread.currentThread().getName() + " finished task at " + format.format(new Date()));
}
}, "Thread-1");
//第二個執行緒開始讀取
Thread thread2 = new Thread(new Runnable() {
public void run() {
long start = System.currentTimeMillis();
//模擬IO的耗時過程
for (;;){
if (System.currentTimeMillis() - start > 1000 * 5){
break;
}
}
System.out.println(Thread.currentThread().getName() + " finished task at " + format.format(new Date()));
}
}, "Thread-2");
System.out.println(Thread.currentThread().getName() + " start task at " + format.format(new Date()));
thread1.start();
thread2.start();
//等待thread1執行完畢
thread1.join();
//等待thread2執行完畢
thread2.join();
System.out.println(Thread.currentThread().getName() + " ended task at " + format.format(new Date()));
}
}
執行結果如下:
main start task at 13:38:28
Thread-2 finished task at 13:38:33
Thread-1 finished task at 13:38:38
main ended task at 13:38:38
可以看到程式很好地完成了功能,實際上join的實現原理是讓當前執行緒不停檢查join的執行緒是否存活,如果join存活則讓當前執行緒永遠等待(join的執行緒執行結束後就不存活了,當前執行緒也不用等待了,這樣就實現了等待join執行緒執行完畢的功能)。join的執行緒終止後,執行緒會呼叫Object.notifyAll()通知等待的執行緒喚醒,這樣就能繼續執行了。
下面的示例演示如何使用CountDownLatch完成同樣的功能:
package com.rhwayfun.concurrency.r0406;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.CountDownLatch;
/**
* Created by rhwayfun on 16-4-6.
*/
public class CountDownLatchDemo {
//引數2表示一個計數器
//這裡可以理解為等待多少個執行緒執行完畢
static CountDownLatch countDownLatch = new CountDownLatch(2);
static final DateFormat format = new SimpleDateFormat("HH:mm:ss");
public static void main(String[] args) throws InterruptedException {
//第一個讀取的執行緒
Thread thread1 = new Thread(new Runnable() {
public void run() {
long start = System.currentTimeMillis();
for (;;){
if (System.currentTimeMillis() - start > 1000 * 10){
break;
}
}
System.out.println(Thread.currentThread().getName() + " finished task at " + format.format(new Date()));
countDownLatch.countDown();
}
});
//第二個執行緒開始讀取
Thread thread2 = new Thread(new Runnable() {
public void run() {
long start = System.currentTimeMillis();
for (;;){
if (System.currentTimeMillis() - start > 1000 * 5){
break;
}
}
System.out.println(Thread.currentThread().getName() + " finished task at " + format.format(new Date()));
countDownLatch.countDown();
}
}, "Thread-2");
System.out.println(Thread.currentThread().getName() + " start task at " + format.format(new Date()));
thread1.start();
thread2.start();
//等待其他執行緒執行完畢
countDownLatch.await();
System.out.println(Thread.currentThread().getName() + " ended task at " + format.format(new Date()));
}
}
執行結果與上面的一樣,這樣就使用CountDownLatch完成同樣的功能。當然,CountDownLatch的功能遠比join強大。CountDownLatch的建構函式會接收一個int型別的額引數作為計數器,如果想等待N個執行緒執行完成,那麼傳入的引數就是N。每次呼叫countDown方法N的值就會減1,await方法會阻塞當前執行緒,直到其他N個執行緒執行完畢。這個時候N變為0。然而,引數N不一定就是指N個執行緒,也可以代表N個步驟等其他的含義。如果某個執行緒需要執行很長時間(比如IO密集型的任務),不可能一直等待,這個時候可以使用另一個帶指定等待時間的方法await(long time,TimeUnit unit),當前執行緒在等待unit的時間後如果仍然沒有執行完畢,那麼就不再等待。
這裡要注意的是CountDownLatch的建構函式傳入的int值必須大於0,如果等於0,呼叫await地方是不會阻塞當前執行緒的。