1. 程式人生 > >Java併發程式設計系列之二十一:CountdownLatch

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地方是不會阻塞當前執行緒的。