【java併發編程】 CountDownLatch、CyclicBarrier、Semaphore區別

1.倒計時協作器CountDownLatch

​ CountDownLatch可以用來實現一個(或者多個)線程等待其他線程完成一組特定的操作之後繼續運行。

​ 場景:cf房間中有10個玩家,9個玩家必須全部準備才能開始遊戲;

代碼實現:

public class CountDownLatchTest {
    public static  final  CountDownLatch countDownLatch = new CountDownLatch(9);
    public static void main(String[] args) {
        new Thread(()->{
            try {
                countDownLatch.await();
                System.out.println("遊戲開始,小心爆頭");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        for(  int i =0;i<9;i++){
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            new Thread(()->{
                countDownLatch.countDown();
                System.out.println(Thread.currentThread().getName()+"玩家準備完畢");
            }).start();
        }
    }
}

​ CountDownLatch內部會維護一個用戶表示未完成的先決操作數量的計數器。CountDownLatch.countDown()方法執行一次,計數器的數量就會減少1。CountDownLatch.await()方法在計數器不爲0的時候,當前線程會被暫停,當計數器達到0時,相應實例上等待的所有線程會別喚醒。

​ 當計數器值達到0時,計數器的值不會再發生便會。此時調用CountDownLatch.countDown()不會有異常拋出,CountDownLatch.await()的線程也不會被暫停。CountDownLatch只能使用一次。CountDownLatch.getCount()方法可以查詢當前計數器的值。

2.柵欄CyclicBarrier

​ 場景:lol遊戲,在選擇完英雄之後,必須等待10名玩家遊戲全部加載完成之後,一起進入遊戲,

​ CyclicBarrier的作用就是等待所有線程全部達到一個點之後再一起執行。

代碼實現:

 public static final   CyclicBarrier cyclicBarrier = new CyclicBarrier(10);
    public static void main(String[] args) {
        while (true){
            for(  int i =0;i<10;i++){
                new Thread(()->{
                    try {
                        try {
                            //休眠一段時間,表示不同的加載時間
                            Random random = new Random();
                            Thread.sleep(random.nextInt(10000));
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName()+"玩家準備完畢");
                        //線程暫停,等待其他玩家加載成功,再一起進入遊戲
                        cyclicBarrier.await();
                        System.out.println(Thread.currentThread().getName()+"進入遊戲");

                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } catch (BrokenBarrierException e) {
                        e.printStackTrace();
                    }

                }).start();
            }

            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //getNumberWaiting 獲取等待的數量
            while (cyclicBarrier.getNumberWaiting()!=0){
                //遊戲沒有結束耐心等待
            }

            System.out.println("遊戲結束,等待遊戲結果");
            System.out.println("下一把開始,請大家準備");

        }

    }

​ 調用CyclicBarrier.await()方法,當前線程被暫停,當最後一個線程調用CyclicBarrier.await()方法時,會使得使用當前實例的暫停的所有線程喚醒。與CountDownLatch的不同點是,當所有線程被喚醒之後,下一次調用await()方法又會暫停,又需要等待最後的線程都執行之後才能喚醒,是可以重複使用的。

​ 注意:CyclicBarrier經常用來模擬高併發的測試,如果一個線程需要等待另一個線程的話,在很多場景Thread.join()即可實現效果。

CyclicBarrier方法

//獲取調用await()方法線程的數量
public int getNumberWaiting();
//獲取屏障的數量
public int getParties();
//等待  返回值爲當前線程的索引,0表示當前線程是最後一個到達的線程
public int await();
public int await(long timeout, TimeUnit unit);
//此屏障是否處於斷開狀態
public boolean isBroken();
//將屏障重置爲初始狀態
//調用初始化之後再await()等的線程會拋異常
public void reset()

3.限流Semaphore

​ 現在服務器有很多種限流的方式,Semaphore是jdk提供的一種限流方式。

@RestController
public class TestController {
    // 限流2 個
    public static final Semaphore semaphore = new Semaphore(2);
    @RequestMapping("/semaphore/test")
    public String test(){
        try {
            semaphore.acquire();
            System.out.println("1-獲取資源");
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            semaphore.release();
        }
        return "success";
    }
    @RequestMapping("/semaphore/test2")
    public String test2(){
        try {
            semaphore.acquire();
            System.out.println("2-獲取資源");
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            semaphore.release();
        }
        return "success";
    }
    @RequestMapping("/semaphore/test3")
    public String test3(){
        try {
            boolean b = semaphore.tryAcquire(1,1000, TimeUnit.MILLISECONDS);
            if(b){
                System.out.println("3-獲取資源");
                semaphore.release();
            }else{
                System.out.println("3-獲取資源失敗");
            }

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "success";
    }
}

按順序請求這三個方法輸出:

1-獲取資源
2-獲取資源
3-獲取資源失敗

當前限流2個,test和test2方法同時獲取資源後等待10秒釋放,test3方法試着獲取資源(等待1秒)。等test和test2有一個調用release釋放方法之後,test3就可以成功獲取資源。

注意:

  • Semaphore.acquire/release方法要配對使用,使用acquire申請資源,使用release釋放資源。Semaphore即使在沒有申請到資源的情況下,還是可以通過release釋放資源,這裏就要自己通過代碼進行合適的處理。和鎖不同的是,鎖必須獲得鎖才能釋放鎖,這裏並沒有這種限制。
  • Semaphore.release方法儘可能的放到finally中避免資源無法歸還。
  • 如果Semaphore的配額爲1,那麼創建的實例相當與一個互斥鎖,由於可以在沒有申請資源的情況調用release釋放資源,所以,這裏允許一個線程釋放另一個線程的鎖。
  • Semaphore在申請資源的時候不是公平的,因此,如果當前配額爲0 時,所有線程在申請的時候是等待狀態,一旦有線程釋放資源,會有等待的線程中任意一個申請成功。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章