1. 程式人生 > >多執行緒之閉鎖(CountDownLatch)

多執行緒之閉鎖(CountDownLatch)

在實際的專案場景中,我們有時候會有以下類似的場景需求:

  • 日終統計時,有多個執行緒併發統計各分類情況,有一個執行緒需要等待每一類都統計完成後進行合計;
  • 或者多個執行緒檢查周邊系統或應用啟動情況,待所有周邊系統啟動成功後再啟動當前應用;

針對以上類似場景JDK提供了非常好用的兩個工具類:CountDownLatch、CyclicBarrier,那兩個之前有什麼區別呢,我們看一下官方解釋:


public class CountDownLatch
extends Object
A synchronization aid that allows one or more threads to wait until a set of operations being performed in other threads completes.
//一個同步的幫助,允許一個或多個執行緒等待,直到在其他執行緒中執行一組操作完成。
A CountDownLatch is initialized with a given count. The await methods block until the current count reaches zero due to invocations of the countDown() method, after which all waiting threads are released and any subsequent invocations of await return immediately. This is a one-shot phenomenon -- the count cannot be reset. If you need a version that resets the count, consider using a CyclicBarrier.
//一個countdown門閂是用一個給定的計數初始化的。等待方法阻塞,直到當前計數達到零,因為呼叫倒計時()方法,在此之後,所有等待執行緒都被釋放,隨後的任何後續呼叫都會立即返回。這是一個單次使用場景,計數不能被重置。如果您需要一個重新設定計數的版本,可以考慮使用一個週期屏障(CyclicBarrier)。

public class CyclicBarrier
extends Object
A synchronization aid that allows a set of threads to all wait for each other to reach a common barrier point. CyclicBarriers are useful in programs involving a fixed sized party of threads that must occasionally wait for each other. The barrier is called cyclic because it can be re-used after the waiting threads are released.
//一種同步的幫助,允許一組執行緒相互等待,從而達到一個共同的障礙點。在涉及固定大小的執行緒的程式中,週期性障礙是有用的,這些執行緒必須等待。這個屏障可以迴圈,因為它可以在等待執行緒被釋放後重新使用。

可以看出CountDownLatch只能使用一次,但CyclicBarrier強調的迴圈(可多次使用),我們來做個小demo來看一下。

CountDownLatch:

從字面意思就可以看出來,CountDownLatch是一個計數器,CountDownLatch的原始碼也比較少,只提供一個建構函式和兩個常用方法:

看下實際使用,假如我跟5個好朋友約定去踢足球,那場地管理員要等我們6個人都到齊後才打開體育場,我們先來看下如果沒有CountDownLatch如何實現以上場景:

import java.util.concurrent.atomic.AtomicInteger;

/**
 * 測試閉鎖
 *
 * @author
 * @create 2018-09-18 14:14
 **/
public class TestCountDownLatch {

    public static void main(String[] args) {

        //七個人開始出發了
        FootballBoy footballBoy = new FootballBoy();
        for (int i=1;i<=7;i++) {
            new Thread(footballBoy,"第" + i + "人").start();
        }
        //場地管理員等著所有人到達
        while (true) {
            if(footballBoy.count.intValue()== 7) {
                System.out.println("人到齊了,開啟場地開始踢球");
                break;
            }
            System.out.println("等待中....");
        }
    }
}

class FootballBoy implements Runnable {

    /**
     * 搞一個共享變數來計數
     */
    public AtomicInteger count = new AtomicInteger(0);

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "到達場地");
        //到達現場報數+1
        count.getAndIncrement();
    }
}

------------------執行結果--------------------

第1人到達場地
第3人到達場地
第2人到達場地
等待中....
等待中....
等待中....
等待中....
等待中....
第5人到達場地
等待中....
等待中....
等待中....
第7人到達場地
等待中....
等待中....
等待中....
第6人到達場地
等待中....
等待中....
等待中....
等待中....
第4人到達場地
等待中....
人到齊了,開啟場地開始踢球


這樣寫起來比較麻煩,看一下使用CountDownLatch之後的demo:

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 測試閉鎖
 * @author jifengzhu
 * @create 2018-09-18 15:24
 **/
public class TestCountDownLatch {

    public static void main(String[] args) {
        //需要提前知道執行執行緒數
        CountDownLatch countDownLatch = new CountDownLatch(7);
        FootballBoy footballBoy = new FootballBoy(countDownLatch);
        for (int i=1;i<=7;i++) {
            new Thread(footballBoy,"第" + i + "人").start();
        }
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            //異常處理
        }
        System.out.println("人到齊了,開啟場地開始踢球...");
    }

}

class FootballBoy implements Runnable {

    private CountDownLatch countDownLatch;

    public FootballBoy(CountDownLatch countDownLatch) {
        this.countDownLatch = countDownLatch;
    }
    @Override
    public void run() {
        try {
            System.out.println(Thread.currentThread().getName() + "到達場地");
        }catch (Exception e) {
            //異常處理
        }finally {
            countDownLatch.countDown();
        }
    }
}

-----------------執行結果---------------
第2人到達場地
第6人到達場地
第3人到達場地
第4人到達場地
第7人到達場地
第1人到達場地
第5人到達場地
人到齊了,開啟場地開始踢球...

通過上面的demo我們可以看出countDownLatch.await();程式碼之後的程式是沒有執行的,直到CountDownLatch的計數器為0才執行,看API和demo發現CountDownLatch不能重複使用,但是如果踢球中場休息,大家各自去上廁所什麼的,等到7個人都休息完回來再開下半場,此時會發現CountDownLatch已經不能滿足了,JDK此時提供了另外一個工具類CyclicBarrier:

import java.util.Random;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 測試迴圈屏障
 *
 * @author
 * @create 2018-09-18 18:12
 **/
public class TestCyclicBarrier {

    public static void main(String[] args) {

        Thread event = new Thread(new Event());
        CyclicBarrier barrier = new CyclicBarrier(7,event);

        for (int i=1;i<=7;i++) {
            new Thread(new FootballBoyer(barrier,i)).start();
        }
    }
}

/**
 * 比賽事件
 */
class Event implements Runnable {

    public static int flag = 1;

    @Override
    public void run() {
        switch (flag) {
            case 1:
                System.out.println("球員到齊,開始比賽......");
                flag++;
                break;
            case 2:
                System.out.println("中場時間到,中場休息......");
                flag++;
                break;
            case 3:
                System.out.println("大家都累了,比賽結束......");
                break;
        }
    }
}

/**
 * 球員
 */
class FootballBoyer implements Runnable {

    private CyclicBarrier barrier;
    private int number;

    public FootballBoyer(CyclicBarrier barrier,int number) {
        this.barrier = barrier;
        this.number = number;
    }
    @Override
    public void run() {
        try {
            System.out.println(number + "號球員到達場地.");
            barrier.await();
            firstHalf(barrier);
            secondHalf(barrier);
        } catch (InterruptedException e) {
        } catch (BrokenBarrierException e) {
        }
    }

    private void firstHalf(CyclicBarrier barrier) {
        System.out.println("上半場" + number + "號球員" + getAction(number));
        try {
            barrier.await();
        } catch (InterruptedException e) {
        } catch (BrokenBarrierException e) {
        }
    }

    private void secondHalf(CyclicBarrier barrier) {
        System.out.println("下半場" + number + "號球員" + getAction(number));
        try {
            barrier.await();
        } catch (InterruptedException e) {
        } catch (BrokenBarrierException e) {
        }
    }

    private String getAction(int number) {
        if(number%2==0) {
            return "踢球";
        }else {
            return "運球";
        }
    }
}


--------------------執行結果-------------------------------
2號球員到達場地.
1號球員到達場地.
4號球員到達場地.
5號球員到達場地.
6號球員到達場地.
3號球員到達場地.
7號球員到達場地.
球員到齊,開始比賽......
上半場7號球員運球
上半場2號球員踢球
上半場1號球員運球
上半場6號球員踢球
上半場5號球員運球
上半場4號球員踢球
上半場3號球員運球
中場時間到,中場休息......
下半場3號球員運球
下半場7號球員運球
下半場5號球員運球
下半場6號球員踢球
下半場2號球員踢球
下半場1號球員運球
下半場4號球員踢球
大家都累了,比賽結束......