大廠面試題:你知道JUC中的Semaphore、CyclicBarrier、CountDownLatch嗎

據說微信搜索《Java魚仔》會變動強哦!java

本文收錄於JavaStarter ,裏面有我完整的Java系列文章,學習或面試均可以看看哦git

(一)概述

資源的分配方式有兩種,一種是獨佔,好比以前講的ReentrantLock,另一種是共享,即咱們今天將要學習的SemaphoreCyclicBarrier以及CountDownLatch。這些都是JUC包中的類。github

(二)Semaphore

Semaphore是信號量的意思,做用是控制訪問特定資源的線程數量。 其核心API爲:面試

semaphore.acquire();
semaphore.release();

這麼說可能比較模糊,下面我舉個例子。微信

Semaphore就比如遊樂園中的某個遊樂設施的管理員,用來控制同時玩這個遊樂設施的人數。好比跳樓機只能坐十我的,就設置Semaphore的permits等於10。ide

每當有一我的來時,首先判斷permits是否大於0,若是大於0,就把一個許可證給這我的,同時本身的permits數量減一。源碼分析

若是permits數量等於0了,其餘人再想進來時就只能排隊了。學習

當一我的玩好以後,這我的把許可證還給Semaphore,permits加1,正在排隊的人再來競爭這一個許可證。ui

下面經過代碼來演示這樣一個場景this

public class SemaphoreTest {
    public static void main(String[] args) {
        //建立permits等於2
        Semaphore semaphore=new Semaphore(2);
        //開五個線程去執行PlayGame
        for (int i = 0; i < 5; i++) {
            new Thread(new PlayGame(semaphore)).start();
        }
    }

    static class PlayGame extends Thread{
        Semaphore semaphore;
        public PlayGame(Semaphore semaphore){
            this.semaphore=semaphore;
        }
        @Override
        public void run() {
            try {
                semaphore.acquire();
                System.out.println(Thread.currentThread().getName()+"得到一個許可證");
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName()+"釋放一個許可證");
                semaphore.release();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

在這裏設置Semaphore的permit等於2,表示同時只有兩個線程能夠執行,而後開五個線程,在執行前經過semaphore.acquire();獲取permit,執行後經過semaphore.release();歸還permit。

在這裏插入圖片描述

經過結果能夠觀察到,每次最多隻會有兩個線程執行PlayGame 。

(三)Semaphore原理

3.1 默認非公平鎖

Semaphore默認建立的是一個非公平鎖:

在這裏插入圖片描述

3.2 Semaphore源碼分析

Semaphore的實現方式和ReentrantLock十分相似。

首先定義一個內部類Sync繼承AbstractQueuedSynchronizer

在這裏插入圖片描述

從Sync的構造方法中能夠看到,初始化時設置state等於permits,在講ReentrantLock的時候,state用來存儲重入鎖的次數,在Semaphore中state用來存儲資源的數量。

Semaphore的核心方法是acquire和release,當執行acquire方法時,sync會執行一個獲取一個共享資源的操做:

在這裏插入圖片描述

核心是判斷剩餘數量是否大於0,若是是的話就經過cas操做去獲取資源,不然就進入隊列中等待

當執行release方法時,sync會執行一個將一個共享資源放回去的cas操做

在這裏插入圖片描述

(四)CountDownLatch

countdownlatch可以讓一個線程等待其餘線程工做完成以後再執行。

countdownlatch經過一個計數器來實現,初始值是指定的數量,每當一個線程完成本身的任務後,計數器減一,當計數器爲0時,執行最後的等待線程。

其核心API爲

CountDownLatch.countDown();
CountDownLatch.await();

下面來看代碼示例:

設定countDownLatch初始值爲2,定義兩個線程分別執行對應的方法,方法執行完畢後再執行countDownLatch.countDown(); 這兩個方法執行的過程當中,主線程被countDownLatch.await();阻塞,只有等到其餘線程都執行完畢以後纔可執行。

public class CountDownLatchTest {
    
    public static void main(String[] args) throws InterruptedException {
        //設定初始值爲2
        CountDownLatch countDownLatch=new CountDownLatch(2);
        //執行兩個任務
        new Thread(new Task1(countDownLatch)).start();
        new Thread(new Task2(countDownLatch)).start();
        //在兩個任務執行完以後纔會執行await方法以後的代碼
        countDownLatch.await();
        System.out.println("其他兩個線程執行完以後執行");
    }

    private static class Task1 implements Runnable {
        private CountDownLatch countDownLatch;
        public Task1(CountDownLatch countDownLatch) {
            this.countDownLatch=countDownLatch;
        }

        @Override
        public void run() {
            System.out.println("執行任務一");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                if (countDownLatch!=null){
                    //執行完畢後調用countDown
                    countDownLatch.countDown();
                }
            }
        }
    }

    private static class Task2 implements Runnable {
        private CountDownLatch countDownLatch;
        public Task2(CountDownLatch countDownLatch) {
            this.countDownLatch=countDownLatch;
        }

        @Override
        public void run() {
            System.out.println("執行任務二");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                if (countDownLatch!=null){
                    //執行完畢後調用countDown
                    countDownLatch.countDown();
                }
            }
        }
    }
}

效果以下:

在這裏插入圖片描述

(五)CyclicBarrier

柵欄屏障,讓一組線程到達一個屏障(也能夠叫同步點)時被阻塞,直到最後一個線程到達屏障時,屏障纔會開門,全部被屏障攔截的線程纔會繼續運行。 其核心API爲:

cyclicBarrier.await();

和countdownlatch的區別在於,countdownlatch是一個線程等待其餘線程執行完畢後再執行,CyclicBarrier是每個線程等待全部線程執行完畢後,再執行。

看代碼,初始化cyclicBarrier爲3,兩個子線程和一個主線程執行完時都會被阻塞在cyclicBarrier.await();代碼前,等三個線程都執行完畢後再執行接下去的代碼。

public class CyclicBarrierTest {
    public static void main(String[] args) throws BrokenBarrierException, InterruptedException {
        CyclicBarrier cyclicBarrier=new CyclicBarrier(3);
        System.out.println("執行主線程");
        new Thread(new Task1(cyclicBarrier)).start();
        new Thread(new Task2(cyclicBarrier)).start();
        cyclicBarrier.await();
        System.out.println("三個線程都執行完畢,繼續執行主線程");
    }

    private static class Task1 implements Runnable {
        private CyclicBarrier cyclicBarrier;
        public Task1(CyclicBarrier cyclicBarrier) {
            this.cyclicBarrier=cyclicBarrier;
        }

        @Override
        public void run() {
            System.out.println("執行任務一");
            try {
                Thread.sleep(2000);
                cyclicBarrier.await();
                System.out.println("三個線程都執行完畢,繼續執行任務一");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        }
    }

    private static class Task2 implements Runnable {
        private CyclicBarrier cyclicBarrier;
        public Task2(CyclicBarrier cyclicBarrier) {
            this.cyclicBarrier=cyclicBarrier;
        }

        @Override
        public void run() {
            System.out.println("執行任務二");
            try {
                Thread.sleep(2000);
                cyclicBarrier.await();
                System.out.println("三個線程都執行完畢,繼續執行任務二");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        }
    }
}

結果以下:

在這裏插入圖片描述

cyclicBarrier還能夠重複執行,而不須要從新去定義。

public static void main(String[] args) throws BrokenBarrierException, InterruptedException {
    CyclicBarrier cyclicBarrier=new CyclicBarrier(3);
    //第一次
    System.out.println("執行主線程");
    new Thread(new Task1(cyclicBarrier)).start();
    new Thread(new Task2(cyclicBarrier)).start();
    cyclicBarrier.await();
    System.out.println("三個線程都執行完畢,繼續執行主線程");
    //第二次
    System.out.println("執行主線程");
    new Thread(new Task1(cyclicBarrier)).start();
    new Thread(new Task2(cyclicBarrier)).start();
    cyclicBarrier.await();
}

(六)總結

歸根結底,Semaphore、CyclicBarrier、CountDownLatch三個類都是對AQS中資源共享的應用,學懂AQS以後,你會發現JUC包中的類變得不難了。好了,咱們下期再見!

相關文章
相關標籤/搜索