java多線程之CountDownLatch倒數閘門

  在多個線程進行協作時,一個常見的情景是一個線程需要等待另外的線程完成某些任務之後才能繼續進行.在這種情況下,可以使用CountDownLatch類,CountDownLatch類相當於多個線程等待開啓的一個閘門.只有在其他線程完成任務之後,閘門纔會打開,等待的線程才能運行.在創建CountDownLatch類的對象是需要指定等待完成的任務數目.一個CountDownLatch.類的對象被執行任務的線程和等待任務完成的線程說共享.當執行任務的線程完成其任務時,調用countDown方法來使待完成的任務數量減1.等待任務完成的線程通過調用await方法進入阻塞狀態直到待完成的任務數量變爲0.當所有任務都完成時,等待任務完成的線程會從await方法返回,可以繼續執行後繼的代碼.CountDownLatch類的對象的使用是一次性的.一旦待完成的任務數量變爲0,再調用await方法就不再阻塞當前線程,而是立即返回.
倒數閘門的使用示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
import java.util.concurrent.CountDownLatch;
 
/**
 * create at 11-9-17
 *
 * @author KETQI
 * @category CountDownLatch主要起倒計時計數器作用,它主要有兩個方法await()和countDown()。
 * 一旦某個線程調用await()方法,那麼該線程就會阻塞,等待CountDownLatch計數器倒計時歸零,
 * 需要注意的是儘管線程調用await()方法後會阻塞,
 * 但是CountDownLatch允許別的線程調用countDown()方法,將計數器減一。
 * 也就是說調用計時器的線程阻塞後,可以利用別的線程控制調用線程何時從新開始運行。
 * <p/>
 * 該demo主要想要做的事就是:在主線程中創建N個子線程,讓支線程等待主線程將開關計數器startSignal打開。
 * 而當主線程打開startSignal開關後,主線程要等待計數器doneSignal歸零,
 * 而doneSignal計數器歸零依賴於每個支線程爲主線程的計數器減一。
 * 所以當主線程打開開關後,支線程才能運行完畢,而只有支線程全部運行完畢,才能打開主線程的計數器。
 * 這樣整個程序才能走完
 */
public class CountDownLatchDemo {
    public static final int N = 5;
 
    public static void main(String[] args) throws InterruptedException {
        // 用於向工作線程發送啓動信號,由主線程調用
        CountDownLatch startSignal = new CountDownLatch(1);
        // 用於等待工作線程的結束信號,由子線程調用
        CountDownLatch doneSignal = new CountDownLatch(N);
        // 創建啓動線程
        System.out.println("開始創建並運行分支線程,且分支線程啓動startSignal計數器,等待主線程將startSignal計數器打開");
        for (int i = 0; i < N; i++) {
            new Thread(new LatchWorker(startSignal, doneSignal), "t" + i).start();
        }
 
        // 主線程,遞減開始計數器,讓所有線程開始工作
        System.out.println("主線程" + Thread.currentThread().getName() + "將startSignal計數器打開");
        startSignal.countDown();
        // 主線程阻塞,等待所有線程完成
        System.out.println("主線程" + Thread.currentThread().getName() + "開始倒計時5個數");
        doneSignal.await();
        /**
         * 爲什麼說運行到下一句,所有線程就全部運行完畢了呢。 因爲主線程要倒計時5個數, 而產生的5個支線程在運行完畢前會將主線程的計數器減一,
         * 所以如果所有支線程運行完畢了 ,主線程才能繼續運行主線程的最後一個打印程序
         */
        System.out.println("所有線程運行完畢");
    }
}
 
class LatchWorker implements Runnable {
    // 用於等待啓動信號
    private final CountDownLatch startSignal;
    // 用於發送結束信號
    private final CountDownLatch doneSignal;
 
    LatchWorker(CountDownLatch startSignal, CountDownLatch doneSignal) {
        this.startSignal = startSignal;
        this.doneSignal = doneSignal;
    }
 
    public void run() {
        try {
            // 一旦調用await()方法,該線程就會開始阻塞。知道計數器startSignal爲0
            System.out.println(Thread.currentThread().getName() + " 開始調用await()方法,等待計數器startSignal被主線程打開");
            startSignal.await();
            doWork();
            System.out.println(Thread.currentThread().getName() + " 將主線程的計數器減一");
            doneSignal.countDown();// 發送完成信號
        catch (InterruptedException ex) {
            ex.printStackTrace();
        }
    }
 
    void doWork() {
        System.out.println(Thread.currentThread().getName() + " 的計數器被打開,分支線程開始運行");
        try {
            Thread.sleep((long) Math.random() * 10000);
        catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章