Java計數器之CountDownLatch、CyclicBarrier、Semaphore


在Java里面有幾種可以用於控制線程狀態的方法,如CountDownLatch計數器、CyclicBarrier循環柵欄、Sempahore信號量。下面就分別演示下他們的使用方法:

CountDownLatch

CountDownLatch可以實現多線程之間的計數器,並實現阻塞功能。比如某個任務依賴於其他的兩個任務,只有那兩個任務執行結束后,它才能執行。

import java.util.concurrent.CountDownLatch;

public class CountDownLatchTest2 {
    public static void main(String[] args) {
        // 創建計數器,初始化為2
        final CountDownLatch latch = new CountDownLatch(2);

        new Thread(() -> {
            try {
                System.out.println("子線程"+Thread.currentThread().getName()+"正在執行");
                Thread.sleep(3000);
                System.out.println("子線程"+Thread.currentThread().getName()+"執行完畢");
                latch.countDown();// 減一
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        new Thread(() -> {
            try {
                System.out.println("子線程"+Thread.currentThread().getName()+"正在執行");
                Thread.sleep(3000);
                System.out.println("子線程"+Thread.currentThread().getName()+"執行完畢");
                latch.countDown();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        try {
            System.out.println("等待2個子線程執行完畢...");
            // 阻塞
            latch.await();
            System.out.println("2個子線程已經執行完畢");
            System.out.println("繼續執行主線程");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

執行的結果:

子線程Thread-0正在執行
等待2個子線程執行完畢...
子線程Thread-1正在執行
子線程Thread-0執行完畢
子線程Thread-1執行完畢
2個子線程已經執行完畢
繼續執行主線程


如上圖所示,線程1需要另兩個線程結束后,才能繼續執行。那么就可以在兩個縣城里面執行countDown(),然后主線程調用await()進行阻塞。

CyclicBarrier 循環柵欄

它有兩層含義,一個是柵欄,一個是循環。先看柵欄,意思就是想一堵牆一樣,可以同時對多個線程狀態進行管理。

如圖所示,幾個線程必須同時執行完,才能繼續:


import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierTest {
    public static void main(String[] args) {
        int N = 4;
        CyclicBarrier barrier  = new CyclicBarrier(N);
        for(int i=0;i<N;i++) {
            new Writer(barrier).start();
        }
    }

    static class Writer extends Thread{
        private CyclicBarrier cyclicBarrier;
        public Writer(CyclicBarrier cyclicBarrier) {
            this.cyclicBarrier = cyclicBarrier;
        }

        @Override
        public void run() {
            System.out.println("線程"+Thread.currentThread().getName()+"正在寫入數據...");
            try {
                Thread.sleep(5000);      //以睡眠來模擬寫入數據操作
                System.out.println("線程"+Thread.currentThread().getName()+"寫入數據完畢,等待其他線程寫入完畢");
                cyclicBarrier.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }catch(BrokenBarrierException e){
                e.printStackTrace();
            }
            System.out.println("所有線程寫入完畢,繼續處理其他任務...");
        }
    }
}

輸出:

線程Thread-0正在寫入數據...
線程Thread-1正在寫入數據...
線程Thread-2正在寫入數據...
線程Thread-3正在寫入數據...
線程Thread-0寫入數據完畢,等待其他線程寫入完畢
線程Thread-1寫入數據完畢,等待其他線程寫入完畢
線程Thread-3寫入數據完畢,等待其他線程寫入完畢
線程Thread-2寫入數據完畢,等待其他線程寫入完畢
所有線程寫入完畢,繼續處理其他任務...
所有線程寫入完畢,繼續處理其他任務...
所有線程寫入完畢,繼續處理其他任務...
所有線程寫入完畢,繼續處理其他任務...

循環的意思就是當計數減到0時,還可以繼續使用,如:


import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierTest3 {
    public static void main(String[] args) {
        int N = 4;
        CyclicBarrier barrier  = new CyclicBarrier(N);

        for(int i=0;i<N;i++) {
            new Writer(barrier).start();
        }

        try {
            Thread.sleep(25000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("CyclicBarrier重用");

        for(int i=0;i<N;i++) {
            new Writer(barrier).start();
        }
    }
    static class Writer extends Thread{
        private CyclicBarrier cyclicBarrier;
        public Writer(CyclicBarrier cyclicBarrier) {
            this.cyclicBarrier = cyclicBarrier;
        }

        @Override
        public void run() {
            System.out.println("線程"+Thread.currentThread().getName()+"正在寫入數據...");
            try {
                Thread.sleep(5000);      //以睡眠來模擬寫入數據操作
                System.out.println("線程"+Thread.currentThread().getName()+"寫入數據完畢,等待其他線程寫入完畢");

                cyclicBarrier.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }catch(BrokenBarrierException e){
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"所有線程寫入完畢,繼續處理其他任務...");
        }
    }

}

輸出:

線程Thread-0正在寫入數據...
線程Thread-2正在寫入數據...
線程Thread-3正在寫入數據...
線程Thread-1正在寫入數據...
線程Thread-2寫入數據完畢,等待其他線程寫入完畢
線程Thread-0寫入數據完畢,等待其他線程寫入完畢
線程Thread-1寫入數據完畢,等待其他線程寫入完畢
線程Thread-3寫入數據完畢,等待其他線程寫入完畢
Thread-3所有線程寫入完畢,繼續處理其他任務...
Thread-1所有線程寫入完畢,繼續處理其他任務...
Thread-0所有線程寫入完畢,繼續處理其他任務...
Thread-2所有線程寫入完畢,繼續處理其他任務...
CyclicBarrier重用
線程Thread-4正在寫入數據...
線程Thread-5正在寫入數據...
線程Thread-6正在寫入數據...
線程Thread-7正在寫入數據...
線程Thread-4寫入數據完畢,等待其他線程寫入完畢
線程Thread-7寫入數據完畢,等待其他線程寫入完畢
線程Thread-5寫入數據完畢,等待其他線程寫入完畢
線程Thread-6寫入數據完畢,等待其他線程寫入完畢
Thread-6所有線程寫入完畢,繼續處理其他任務...
Thread-7所有線程寫入完畢,繼續處理其他任務...
Thread-4所有線程寫入完畢,繼續處理其他任務...
Thread-5所有線程寫入完畢,繼續處理其他任務...

Semaphore信號量

這個東西有點像連接池的感覺,某一時間只有幾個線程能拿到資源,執行操作。

比如下面車間工人在排隊使用機器的例子:


import java.util.concurrent.Semaphore;

public class SemaphoreTest {
    public static void main(String[] args) {
        int N = 8;            //工人數
        Semaphore semaphore = new Semaphore(5); //機器數目
        for(int i=0;i<N;i++) {
            new Worker(i, semaphore).start();
        }
    }

    static class Worker extends Thread{
        private int num;
        private Semaphore semaphore;
        public Worker(int num,Semaphore semaphore){
            this.num = num;
            this.semaphore = semaphore;
        }

        @Override
        public void run() {
            try {
                semaphore.acquire();
                System.out.println("工人"+this.num+"占用一個機器在生產...");
                Thread.sleep(2000);
                System.out.println("工人"+this.num+"釋放出機器");
                semaphore.release();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

輸出:

工人0占用一個機器在生產...
工人1占用一個機器在生產...
工人2占用一個機器在生產...
工人3占用一個機器在生產...
工人4占用一個機器在生產...
工人1釋放出機器
工人0釋放出機器
工人4釋放出機器
工人5占用一個機器在生產...
工人2釋放出機器
工人7占用一個機器在生產...
工人3釋放出機器
工人6占用一個機器在生產...
工人5釋放出機器
工人6釋放出機器
工人7釋放出機器

總結

  1. CountDownLatch 可以實現計數等待,主要用於某個線程等待其他幾個線程
  2. CyclicBarrier 實現循環柵欄,主要用於多個線程同時等待其他線程
  3. Semaphore 信號量,主要強調只有某些個數量的線程能拿到資源執行

參考


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM