天天看點

多線程002 - 再談CountDownLatch

 java.util.concurrent.CountDownLatch是JDK 1.5提供的一個同步輔助類:在一組正在其他線程中的操作執行完成之前,它允許一個或多個線程一直等待。

 初始化CountDownLatch時需要指定計數。通過調用countDown方法使目前計數到達零之前,await方法會一直阻塞。之後,所有等待的線程會被釋放,await後面的操作也會立即執行。因為計數無法被重置,是以這種操作隻會出現一次。如果需要重置計數,請考慮使用java.util.concurrent.CyclicBarrier。

 CountDownLatch是一個通用同步工具,它有很多用途。用1初始化的CountDownLatch可以用作一個簡單的開/關鎖存器或入口:在某一線程調用countDown打開入口前,所有調用await的線程都一直在入口處等待。用N(N>=1)初始化的 CountDownLatch可以使一個線程在N個線程完成某項操作之前一直等待,或者使其在某項操作完成N次之前一直等待。

 CountDownLatch的一個有用特性是,在計數達到0之前,它不會阻塞調用countDown方法的線程繼續執行,它隻是阻止所有調用await的線程繼續執行await後面的操作。

CountDownLatch有兩種典型用法:

有兩個計數器,一個啟動信号,一個完成信号。

将一個問題分成N個部分,需要執行的N個子部分定義為Runnable,然後将所有Runnable加入到Executor隊列中。當所有的N個子部分完成後,等待的線程将會通過await方法繼續執行後續操作。

兩個計數器

示例代碼:

public class CountDownLatchTest {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch startSignal = new CountDownLatch(1);// 先行條件
        CountDownLatch doneSignal = new CountDownLatch(N);
 
        for (int i = 0; i < N; ++i) {
            new Thread(new Worker(startSignal, doneSignal)).start();
        }
 
        doSomethingElse();
        startSignal.countDown();// 開始
        doSomethingElse();
        doneSignal.await();// 等待所有操作結束
    }
}
 
class Worker implements Runnable {
    private final CountDownLatch startSignal;
    private final CountDownLatch doneSignal;
 
    Worker(CountDownLatch startSignal, CountDownLatch doneSignal) {
        this.startSignal = startSignal;
        this.doneSignal = doneSignal;
    }
 
    @Override
    public void run() {
        try {
            startSignal.await();// 等待先行條件結束
            doWork();
            doneSignal.countDown();
        } catch (InterruptedException ex) {
        }
    }
 
    void doWork() {...}
}      

 在示例代碼中,startSignal為先決條件,此處初始計數為1,是一個簡單的開關。比如田徑比賽中的百米跑,發令槍響前,運動員都在等待;發令槍響後,運動員開始比賽,等到最後一名運動員到達終點,比賽結束。

簡單的代碼實作為:

package howe.demo.thread.countdown;
 
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
 
/**
 * @author liuxinghao
 * @version 1.0 Created on 2014年9月15日
 */
public class CountDownLatchTest {
    public static void main(String[] args) throws InterruptedException {
        final CountDownLatch begin = new CountDownLatch(1);
        final CountDownLatch end = new CountDownLatch(3);
        final ExecutorService exec = Executors.newFixedThreadPool(3);
 
        for (int index = 0; index < 3; index++) {
            exec.submit(new Runner(index + 1, begin, end));
        }
 
        System.out.println("各就各位。。。");
        System.out.println("預備。。。");
        System.out.println("嘭。。。");
        begin.countDown();
        end.await();
        System.out.println("比賽結束,準備頒獎。");
        exec.shutdown();
    }
}
 
class Runner implements Runnable {
    private int no;
    private CountDownLatch begin;
    private CountDownLatch end;
 
    public Runner(int no, CountDownLatch begin, CountDownLatch end) {
        this.no = no;
        this.begin = begin;
        this.end = end;
        System.out.println("No." + no + "到達起跑線。");
    }
 
    @Override
    public void run() {
        try {
            begin.await();// 等待發令槍響
            System.out.println("No." + no + "向前飛奔着。。。");
            TimeUnit.SECONDS.sleep(new Random().nextInt(10));// 奔跑的過程中。。。
        } catch (InterruptedException e) {
        }
        System.out.println("No." + no + "到達終點。");
        end.countDown();
    }
}      

 引申開來,開始定義一系列的執行的鍊條,第一個沒有先決條件,直接執行,第二個以第一個為先決條件,以此類推。我是懶人,不做太多贅述,我一個朋友的文章中寫的不錯:利用CountDownLatch同步輔助類進行線程同步

一個計數器

 一個計數器的情況自己感覺情況比較單一,就是主線程等待子線程結束,再繼續執行。這裡的主線程、子線程是相對而言的,可能主線程本身是另一個線程的子線程。

public class CountDownLatchTest6 {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch doneSignal = new CountDownLatch(3);
        ExecutorService e = Executors.newCachedThreadPool();
 
        for (int i = 0; i < 3; ++i) {
            e.execute(new Worker(doneSignal, i));
        }
        doSomethingElse();
        doneSignal.await();// 等待子線程結束
        doSomethingElse();
        e.shutdown();
    }
}
 
class Worker implements Runnable {
    private final CountDownLatch doneSignal;
    private final int id;
 
    Worker(CountDownLatch doneSignal, int id) {
        this.doneSignal = doneSignal;
        this.id = id;
    }
 
    @Override
    public void run() {
        doWork();
        doneSignal.countDown();
    }
 
    void doWork() {...}
}      

 示例代碼中的doSomethingElse方法可以是一些業務邏輯代碼,根據具體功能發生變化。

 對于這種方式可以參看上一篇中關于老闆和勞工的例子中的第二種解決方法多線程001 - 主線程等待子線程結束,這裡也不做贅述。