一文搞懂四種同步工具類

CountDownLatch

解釋:

CountDownLatch至關於一個門閂,門閂上掛了N把鎖。只有N把鎖都解開的話,門纔會打開。怎麼理解呢?我舉一個賽跑比賽的例子,賽跑比賽中必須等待全部選手都準備好了,裁判才能開發令槍。選手才能夠開始跑。CountDownLatch當中主要有兩個方法,一個是await()會掛上鎖阻塞當前線程,至關於裁判站在起始點等待,等待各位選手準備就緒,一個是countDown方法用於解鎖,至關於選手準備好了以後調用countDown方法告訴裁判本身準備就緒,當全部人都準備好了以後裁判開發令槍。多線程

代碼:

public class TestCountDownLatch {
    public static void main(String[] args) {
        // 須要等待兩個線程,因此傳入參數爲2
        CountDownLatch latch = new CountDownLatch(2);
        // 該線程運行1秒
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("1號選手準備就緒!用時1秒!");
                latch.countDown();
            }
        }).start();
        
        // 該線程運行3秒
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("2號選手準備就緒!用時3秒!");
                latch.countDown();
            }
        }).start();
        
        try {
            System.out.println("請1號選手和2號選手各就各位!");
            // 主線程在此等待兩個線程執行完畢以後繼續執行
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 兩個線程執行完畢後,主線程恢復運行
        System.out.println("裁判發槍,1號選手和2號選手開跑!");
    }
}

運行結果:

請1號選手和2號選手各就各位!
1號選手準備就緒!用時1秒!
2號選手準備就緒!用時3秒!
裁判發槍,1號選手和2號選手開跑!

若是去掉CountDownLatch的效果呢?運行結果就會變成以下:ide

請1號選手和2號選手各就各位!
裁判發槍,1號選手和2號選手開跑!
1號選手準備就緒!用時1秒!
2號選手準備就緒!用時3秒!

裁判就會在選手還未準備就緒的時候開發令槍,這就亂套了。
其實CountDownLatch一個最簡單的用處就是計算多線程執行完畢時的時間。像剛纔的例子當中兩個線程並行執行了共花費了3秒鐘。工具

CyclicBarrier

解釋:

CyclicBarrier就像一個柵欄,將各個線程攔住。Cyclic是循環的英文,代表該工具能夠進行循環使用。CyclicBarrier(N)的構造參數代表該一共有幾個線程須要互相等待。它至關於N個選手約定進行屢次比賽,每次比賽完都要在起跑點互相等待。讀者可能會立刻疑惑這不是和CountDownLatch同樣嗎?不同。由於CountDownLatch是裁判等待選手,是調用await()方法的線程,等待調用countDown()方法的各個線程。而CyclicBarrier是選手等待選手,是調用await()方法的線程互相等待,等待其餘線程都運行好以後,再開始下一輪運行。 ui

咱們舉一個例子,兩個選手進行比賽,一共進行三輪比賽。線程

代碼:

public class TestCyclicBarrier {
    // 1號選手跑的輪數
    public static int countA = 1;
    // 2號選手跑的輪數
    public static int countB = 1;
    public static void main(String[] args) {
        // 填入2,表明2個線程互相等待
        CyclicBarrier barrier = new CyclicBarrier(2);

        new Thread(new Runnable() {
            @Override
            public void run() {
                // 一共跑三輪
                for (int i = 0; i < 3; i++) {
                    System.out.println("1號選手開始跑!當前第" + countA++ + "輪比賽!");
                    // 1號選手跑得慢,每次跑三秒
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    try {
                        System.out.println("1號選手抵達終點!");
                        // 調用等待方法,在此等待其餘選手
                        barrier.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } catch (BrokenBarrierException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();


        new Thread(new Runnable() {
            @Override
            public void run() {
                // 一共等待三輪
                for (int i = 0; i < 3; i++) {
                    System.out.println("2號選手開始跑!當前第" + countB++ + "輪比賽!");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    try {
                        System.out.println("2號選手抵達終點!");
                        // 調用等待方法,在此等待其餘選手
                        barrier.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } catch (BrokenBarrierException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }

}

運行結果:

1號選手開始跑!當前第1輪比賽!
2號選手開始跑!當前第1輪比賽!
2號選手抵達終點!
1號選手抵達終點!
1號選手開始跑!當前第2輪比賽!
2號選手開始跑!當前第2輪比賽!
2號選手抵達終點!
1號選手抵達終點!
1號選手開始跑!當前第3輪比賽!
2號選手開始跑!當前第3輪比賽!
2號選手抵達終點!
1號選手抵達終點!

每輪比賽1號選手和2號選手都會回到起跑線互相等待,再開啓下一輪比賽。
若是不加CyclicBarrier呢?code

1號選手開始跑!當前第1輪比賽!
2號選手開始跑!當前第1輪比賽!
2號選手抵達終點!
2號選手開始跑!當前第2輪比賽!
2號選手抵達終點!
2號選手開始跑!當前第3輪比賽!
1號選手抵達終點!
1號選手開始跑!當前第2輪比賽!
2號選手抵達終點!
1號選手抵達終點!
1號選手開始跑!當前第3輪比賽!
1號選手抵達終點!

此時2號選手就直接跑完三輪比賽,不等1號選手了。遊戲

Semaphore

Semaphore英文的字面意思是信號量。它的工做機制是每一個線程想要獲取運行的機會的話,都必須獲取到信號量。acquire()方法阻塞的獲取信號量,release()釋放信號量。舉個例子,假設咱們去迪士尼遊玩,可是迪士尼擔憂遊客不少的話,影響你們的遊玩體驗,因而規定每一個小時只能賣出兩張門票。這樣就能夠控制在遊樂園當中的遊客數量了。開發

代碼:

public class TestSemaphore {

    public static void main(String[] args) {

        Semaphore semaphore = new Semaphore(0);
        System.out.println("顧客在售票處等候中");
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (; ; ) {
                    try {
                        Thread.sleep(500);
                        // 等待出票
                        semaphore.acquire();
                        System.out.println("顧客拿到門票入場!");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {

                for (int i = 0; i < 3; i++) {
                    try {
                        // 等待一小時再發門票
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    // 一次性發出兩張門票
                    System.out.println("售票處第" + (i + 1) + "小時售出兩張票!");
                    semaphore.release();
                    semaphore.release();
                }
            }
        }).start();

        System.out.println("售票處開始售票!");
    }
}

運行結果:

顧客在售票處等候中...
售票處開始售票!
售票處第1小時售出兩張票!
顧客拿到門票入場!
顧客拿到門票入場!
售票處第2小時售出兩張票!
顧客拿到門票入場!
顧客拿到門票入場!
售票處第3小時售出兩張票!
顧客拿到門票入場!
顧客拿到門票入場!

Exchanger

解釋:

Exchanger提供了讓兩個線程互相交換數據的同步點。Exchanger有點像2個線程的CyclicBarrier,線程之間都是互相等待,區別在於Exchanger多了交換的操做。舉個例子比如之前玩網遊的時候,買家和賣家必須走到地圖上同一地點面對面進行交易同樣,一手交錢一手交裝備。get

代碼:

public class TestExchanger {
    public static void main(String[] args) {
        Exchanger<String> exchanger = new Exchanger<>();
        
        new Thread(new Runnable() {
            @Override
            public void run() {
                String weapon = "裝備";
                System.out.println("我是賣家,我帶着" + weapon + "過來了!");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("賣家到達地圖上交易地點");
                try {
                    System.out.println("我是賣家,換回了" + exchanger.exchange(weapon));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                String money = "一萬遊戲幣";
                System.out.println("我是買家,我帶着" + money + "過來了");
                try {
                    Thread.sleep(4000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("買家到達地圖上交易地點");
                try {
                    System.out.println("我是買家,換回了" + exchanger.exchange(money));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        }).start();
    }

運行結果:

我是賣家,我帶着裝備過來了!
我是買家,我帶着一萬遊戲幣過來了
賣家達到交易地點
買家到達交易地點
我是買家,換回了裝備
我是賣家,換回了一萬遊戲幣

若有錯誤,懇請網友評論指正。

轉自個人我的博客 vc2x.com同步

相關文章
相關標籤/搜索