Java多執行緒同步工具類之CyclicBarrier

bigfan發表於2019-06-30

一、CyclicBarrier使用

CyclicBarrier從字面上可以直接理解為執行緒執行的屏障,它可以讓一組執行緒執行到一個共同的屏障點時被阻塞,直到最後一個執行緒執行到指定位置,你設定的執行執行緒就會觸發執行;同時CyclicBarrier相比與CountDownLatch,它是可以被重置的;下面我們通過一個簡單例子看下CyclicBarrier的使用;

例項化一個CyclicBarrier物件並傳入你要控制的執行緒內部;

    public static void main(String[] args) {

        CyclicBarrier cb = new CyclicBarrier(3, new Runnable() {
            public void run() {
                System.out.println("所有執行緒集合");
            }
        });
        for (int i = 0; i < 3; i++) {
            new CyclicBarrierThread(i + "", cb).start();
        }

    }

計數執行緒程式碼,每當計數到偶數時呼叫CyclicBarrier的await()方法

public class CyclicBarrierThread extends Thread{
    
    private CyclicBarrier barrier;
    
    private String name;
    
    private int count;
    
    public CyclicBarrierThread(String name,CyclicBarrier barrier) {
        this.name=name;
        this.barrier=barrier;
        this.count=0;
    }
    
    public void run() {
        try {
            for(int i=0;i<10;i++) {
                
                Thread.sleep(100);
                count++;
                System.out.println(name+"號執行緒---"+Thread.currentThread().getName()+"開始計數:"+count);
                if(count%2==0) {//每計數到偶數次時集合一次
                    barrier.await();
                    System.out.println(name+"號執行緒---"+Thread.currentThread().getName()+"集合完畢,繼續計數");
                }
            }
            
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }
}

檢視程式碼輸出

2號執行緒---Thread-2開始計數:1
0號執行緒---Thread-0開始計數:1
1號執行緒---Thread-1開始計數:1
2號執行緒---Thread-2開始計數:2
1號執行緒---Thread-1開始計數:2
0號執行緒---Thread-0開始計數:2
所有執行緒集合
2號執行緒---Thread-2集合完畢,繼續計數
1號執行緒---Thread-1集合完畢,繼續計數
0號執行緒---Thread-0集合完畢,繼續計數
2號執行緒---Thread-2開始計數:3
1號執行緒---Thread-1開始計數:3
0號執行緒---Thread-0開始計數:3
2號執行緒---Thread-2開始計數:4
0號執行緒---Thread-0開始計數:4
1號執行緒---Thread-1開始計數:4
所有執行緒集合
1號執行緒---Thread-1集合完畢,繼續計數
2號執行緒---Thread-2集合完畢,繼續計數
0號執行緒---Thread-0集合完畢,繼續計數
0號執行緒---Thread-0開始計數:5
2號執行緒---Thread-2開始計數:5
1號執行緒---Thread-1開始計數:5
0號執行緒---Thread-0開始計數:6
1號執行緒---Thread-1開始計數:6
2號執行緒---Thread-2開始計數:6
所有執行緒集合
2號執行緒---Thread-2集合完畢,繼續計數
0號執行緒---Thread-0集合完畢,繼續計數
1號執行緒---Thread-1集合完畢,繼續計數
0號執行緒---Thread-0開始計數:7
1號執行緒---Thread-1開始計數:7
2號執行緒---Thread-2開始計數:7
1號執行緒---Thread-1開始計數:8
0號執行緒---Thread-0開始計數:8
2號執行緒---Thread-2開始計數:8
所有執行緒集合
2號執行緒---Thread-2集合完畢,繼續計數
0號執行緒---Thread-0集合完畢,繼續計數
1號執行緒---Thread-1集合完畢,繼續計數
0號執行緒---Thread-0開始計數:9
1號執行緒---Thread-1開始計數:9
2號執行緒---Thread-2開始計數:9
1號執行緒---Thread-1開始計數:10
0號執行緒---Thread-0開始計數:10
2號執行緒---Thread-2開始計數:10
所有執行緒集合
1號執行緒---Thread-1集合完畢,繼續計數
2號執行緒---Thread-2集合完畢,繼續計數
0號執行緒---Thread-0集合完畢,繼續計數

通過輸出結果可以看到,計數執行緒每計數到偶數次時使用CyclicBarrier的await()方法,執行緒都會進入阻塞等待的狀態,直到最後一個執行緒到達屏障點時,觸發你定義的執行執行緒,而且CyclicBarrier的await()方法是可以重複使用的。

二、CyclicBarrier原始碼分析

下面我們就對CyclicBarrier內部的原始碼實現進行一些分析與總結

1、CyclicBarrier的構造

首先看下CyclicBarrier的建構函式

    public CyclicBarrier(int parties, Runnable barrierAction) {
        if (parties <= 0) throw new IllegalArgumentException();
        //攔截的執行緒數量
        this.parties = parties;
        //用於計數的count值,每有一個執行緒執行到屏障點,就會遞減1
        this.count = parties;
        //定義的攔截執行緒
        this.barrierCommand = barrierAction;
    }

CyclicBarrier的建構函式很簡單就是接收你要攔截的執行緒數量與定義的執行執行緒。

2、await方法

    public int await() throws InterruptedException, BrokenBarrierException {
        try {
            return dowait(false, 0L);
        } catch (TimeoutException toe) {
            throw new Error(toe); // cannot happen
        }
    }

我們看下具體實現dowait方法的實現

    private int dowait(boolean timed, long nanos)
        throws InterruptedException, BrokenBarrierException,
               TimeoutException {
        //獲取可重入鎖
        final ReentrantLock lock = this.lock;
        //加鎖
        lock.lock();
        try {
            //CyclicBarrier內部定義的一個Generation類
            final Generation g = generation;

            //判斷Generation的broken狀態
            if (g.broken)
                throw new BrokenBarrierException();

            //如果執行緒被中斷
            if (Thread.interrupted()) {
                //Generation的broken置為true,count值重置,並喚醒所有執行緒
                breakBarrier();
                throw new InterruptedException();
            }

            //count值減一
            int index = --count;
            if (index == 0) {  // 如果conunt為0,說明最後一個執行緒到大屏障
                boolean ranAction = false;
                try {
                    final Runnable command = barrierCommand;
                    if (command != null)
                        command.run();//執行你傳入的執行緒
                    ranAction = true;
                    nextGeneration();//喚醒所有阻塞的執行緒,同時重置count值與Generation
                    return 0;
                } finally {
                    if (!ranAction)
                        //攔截執行緒沒有正常執行,喚醒所有執行緒,同時重置count值,Generation的broken置為true
                        breakBarrier();
                }
            }

            // loop until tripped, broken, interrupted, or timed out
            for (;;) {
                try {
                    //是否設定阻塞的超時時間
                    if (!timed)
                        //釋放當前鎖
                        trip.await();//false 表示不設定,一直阻塞
                    else if (nanos > 0L)
                        nanos = trip.awaitNanos(nanos);//true 設定阻塞的超時時間
                } catch (InterruptedException ie) {
                    if (g == generation && ! g.broken) {
                        breakBarrier();
                        throw ie;
                    } else {
                        // We're about to finish waiting even if we had not
                        // been interrupted, so this interrupt is deemed to
                        // "belong" to subsequent execution.
                        Thread.currentThread().interrupt();
                    }
                }

                if (g.broken)
                    throw new BrokenBarrierException();

                if (g != generation)
                    return index;

                if (timed && nanos <= 0L) {
                    breakBarrier();
                    throw new TimeoutException();
                }
            }
        } finally {
            //釋放鎖
            lock.unlock();
        }
    }

dowait方法的實現流程是很清晰的,通過ReentrantLock的Condition介面與count值相互配合,主要完成以下功能:

1、當需要攔截的執行緒到達屏障點呼叫await方法後獲取ReentrantLock鎖,保證執行緒安全;

2、檢查count值是否為0,判斷是否是最後一個執行緒到達屏障,如果是的話執行需要觸發執行的執行緒,呼叫Condition的signalAll方法喚醒所有阻塞的執行緒,並重置count值與Generation類,保障CyclicBarrier的重複可用;

3、如果不是最後一個執行緒的話,根據傳入的引數呼叫Condition的await方法釋放鎖資源並進入阻塞等待,直到被喚醒;

3、reset方法

可以用來主動重置CyclicBarrier的狀態

    public void reset() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            //generation.broken設定為true,喚醒所有執行緒,count值重置
            breakBarrier();   
            nextGeneration(); 
        } finally {
            lock.unlock();
        }
    }
    
    
    private void nextGeneration() {
        // signal completion of last generation
        trip.signalAll();
        // set up next generation
        count = parties;
        generation = new Generation();
    }


    private void breakBarrier() {
        generation.broken = true;
        count = parties;
        trip.signalAll();
    }

breakBarrier()與nextGeneration(),這兩個方法的主要區別就在於前者會把generation.broken設定為true,也就是說如果呼叫reset方法主動重置CyclicBarrier類的狀態,當前正在使用CyclicBarrier類同步的執行緒都會被喚醒或丟擲異常;

4、getNumberWaiting方法

    public int getNumberWaiting() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return parties - count;
        } finally {
            lock.unlock();
        }
    }

很明顯getNumberWaiting方法使用來獲取當前已經執行至遮蔽點並阻塞等待的執行緒數量的;

三、總結

通過上面分析可以看到CyclicBarrier的實現原理相對還是比較簡單與清晰的,主要是基於ReentrantLock與計數器相結合來實現多個執行緒的同步控制的。以上就是對CyclicBarrier類的使用與內部實現進行的分析,其中如有不足與不正確的地方還望指出與海涵。

 

關注微信公眾號,檢視更多技術文章。

 

相關文章