1. 程式人生 > >Java併發程式設計之CountDownLatch,CyclicBarrier實現一組執行緒相互等待、喚醒

Java併發程式設計之CountDownLatch,CyclicBarrier實現一組執行緒相互等待、喚醒

java多執行緒應用場景不少,有時自己編寫程式碼又不太容易實現,好在concurrent包提供了不少實現類,還有google的guava包更是提供了一些最佳實踐,這讓我們在面對一些多執行緒的場景時,有了不少的選擇。

這裡主要是看幾個涉及到多執行緒等待的工具類。

一 CountDownLatch 一個或多個執行緒等待其他執行緒達到某一個目標後,再進行自己的下一步工作。而被等待的“其他執行緒”達到這個目標後,也繼續自己下面的任務

看起來雖然比較繞,但是還算能理解。看幾個場景:跑步比賽,裁判需要等到所有的運動員(“其他執行緒”)都跑到終點(達到目標),才能去算排名和頒獎。模擬併發,我需要啟動100個執行緒去同時訪問某一個地址,我希望它們能同時併發,而不是一個一個的去執行。用法很簡單,只有一個構造方法用於指明計數數量,然後就是await用於執行緒等待,countDown用於將計數器減1.
public CountDownLatch(int count) {  };  //引數count為計數值
public void await() throws InterruptedException { };   //呼叫await()方法的執行緒會被掛起,它會等待直到count值為0才繼續執行
public boolean await(long timeout, TimeUnit unit) throws InterruptedException { };  //和await()類似,只不過等待一定的時間後count值還沒變為0的話就會繼續執行
public void countDown() { };  //將count值減1
工作原理就是讓“其他執行緒”在合適的地方進行await等待,直到所有執行緒達到了某一個目標,然後再一起釋放。看程式碼:賽跑的程式碼
import java.util.Random;
import java.util.concurrent.CountDownLatch;

/**
 * Created by wuwf on 17/7/17.
 * 一個執行緒或多個執行緒等待其他執行緒執行達到某一目標後進行自己的下一步工作,而被等待的“其他執行緒”達到這個目標後繼續自己下面的任務。
 * <p/>
 *
 */
public class TestCountDownLatch {
    private CountDownLatch countDownLatch = new CountDownLatch(4);

    public static void main(String[] args) {
        TestCountDownLatch testCountDownLatch = new TestCountDownLatch();
        testCountDownLatch.begin();
    }

    /**
     * 運動員
     */
    private class Runner implements Runnable {
        private int result;
        public Runner(int result) {
            this.result = result;
        }

        @Override
        public void run() {
            try {
                //模擬跑了多少秒,1-3之間隨機一個數
                Thread.sleep(result * 1000);

                System.out.println("運動員" + Thread.currentThread().getId() + "跑了" + result + "秒");

                //跑完了就計數器減1
                countDownLatch.countDown();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private void begin() {
        System.out.println("賽跑開始");

        Random random = new Random(System.currentTimeMillis());
        for (int i = 0; i < 4; i++) {
            //隨機設定每個運動員跑多少秒結束
            int result = random.nextInt(3) + 1;
            new Thread(new Runner(result)).start();
        }

        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("所有人都跑完了,裁判開始算成績");
    }
}
這段程式碼就是一個主執行緒,等待其他多個子執行緒完成某個任務後,再去執行下面的邏輯。

模擬併發的程式碼
import java.util.concurrent.CountDownLatch;

/**
 * Created by wuwf on 17/7/18.
 * 模擬N個執行緒同時啟動
 */
public class TestManyThread {
    private CountDownLatch countDownLatch = new CountDownLatch(200);

    public static void main(String[] args) {
        new TestManyThread().begin();
    }
    
    public void begin() {
        for (int i = 0; i < 200; i++) {
            new Thread(new UserThread()).start();
            countDownLatch.countDown();
        }

        try {
            Thread.sleep(2000);
            System.out.println("執行緒併發");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private class UserThread implements Runnable {

        @Override
        public void run() {
            try {
                //等待所有執行緒
                countDownLatch.await();

                //TODO 在這裡做客戶端請求,譬如訪問資料庫之類的操作
                
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

需要注意,上面兩個例子是不同的方式,一個是主執行緒等待其他執行緒達到某個目標後,自己再去完成一件事;第二個例子是多個執行緒達到某個目標後,繼續完成各自的後續任務。
其中由第二個例子引申出下一個併發工具類。

二 CyclicBarrier 實現讓一組執行緒等待至某個狀態之後再全部同時執行,而且當所有等待執行緒被釋放後,CyclicBarrier可以被重複使用。

這個也很好理解,大家約定好一起到XX地址匯合,所有人都到了以後再一起去吃飯。它有兩個構造方法
public CyclicBarrier(int parties, Runnable barrierAction) {
}
 
public CyclicBarrier(int parties) {
}
parties代表讓多少個執行緒等待,Runnable屬性是一個新執行緒,代表所有執行緒達到狀態、等待完畢後,會執行的任務。
同樣的也有兩個await方法
public int await() throws InterruptedException, BrokenBarrierException { };
public int await(long timeout, TimeUnit unit)throws InterruptedException,BrokenBarrierException,TimeoutException { };
第一個代表執行緒掛起開始等待,一直等到達到目標狀態,第二個代表等待一段指定的時間後,如果還沒釋放,就直接繼續執行,不await了。
看程式碼
import java.util.concurrent.CyclicBarrier;

/**
 * Created by wuwf on 17/7/18.
 */
public class TestCyclicBarrier {
    private CyclicBarrier cyclicBarrier = new CyclicBarrier(5);

    public static void main(String[] args) {
        new TestCyclicBarrier().begin();
    }

    public void begin() {
        for (int i = 0; i < 5; i++) {
            new Thread(new Student()).start();
        }

    }

    private class Student implements Runnable {

        @Override
        public void run() {
            try {
                System.out.println("學生" + Thread.currentThread().getId() + "正在趕往XX飯店的路上");
                Thread.sleep(2000);
                //到了就等著,等其他人都到了,就進飯店
                cyclicBarrier.await();
            } catch (Exception e) {
                e.printStackTrace();
            }

            System.out.println("大家都到了,進去吃飯吧!");
        }
    }
}


可以看到這個工具類的目的,主要是為了限制一組執行緒在達到某個條件後,強制進行等待,直到最後一個執行緒也執行完await之前的邏輯後,再所有執行緒一起走await後面的。與CountDownLatch不同,它不需要去自己維護那個CountDown每次減1的操作,與之相反,CyclicBarrier是每次都加1,直到加到構造方法裡設定的值。所以使用這個類一樣可以完成上面CountDownLatch的第二個例子,就是模擬N個執行緒併發,而且用法比CountDownLatch更簡單一些。要說起這兩個類的區別,我覺得更多的初衷是CountDownLatch目的是讓一個執行緒等待其他N個執行緒達到某個條件後,自己再去做某個事(通過CyclicBarrier的第二個構造方法,在新執行緒裡做事可以達到同樣的效果)。而CyclicBarrier的目的是讓N多執行緒互相等待直到所有的都達到某個狀態,然後這N個執行緒再繼續執行各自後續(通過CountDownLatch在某些場合也能完成類似的效果)。根據場景選擇更簡單的那個就好。