CyclicBarrier原來是這樣的

Liusy01發表於2020-10-13

上一篇聊了一下Semaphore訊號燈的用法及原始碼,這一篇來聊一下CyclicBarrier的用法及解析。

官網解釋:

  • 允許一組執行緒全部等待彼此達到共同屏障點的同步輔助。迴圈阻塞在涉及固定大小的執行緒方的程式中很有用,這些執行緒必須偶爾等待彼此。屏障被稱為迴圈 ,因為它可以在等待的執行緒被釋放之後重新使用。

意思就是每個執行緒都得執行到等待點進行等待,直到所有執行緒都執行到等待點,才會繼續往下執行。相當於日常開會,只有等每個參會的人都到之後才會開始會議。

 

CyclicBarrier原來是這樣的

 

用法:(以開會舉例)

1、會議需要三個人
CyclicBarrier cyclicBarrier = new CyclicBarrier(3, new Runnable() {
        @Override
        public void run()
            2、這是三個人都到齊之後會執行的程式碼
            System.out.println("三個人都已到達會議室")
        }
    });

3、定義三個執行緒,相當於三個參會的人
  for (int i = 0; i < 3; i++) {
      final int finalI = i;
      new Thread(new Runnable() {
          @Override
          public void run() {
              try {
                  4、模擬每人到會議室所需時間 
                  Thread.sleep((long) (Math.random()*5000));
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
              System.out.println("第"+Thread.currentThread().getName()+"個人到達會議室");
              try {
                  5、等待其他人到會議室 
                  cyclicBarrier.await();
              } catch (InterruptedException e) {
                  e.printStackTrace();
              } catch (BrokenBarrierException e) {
                  e.printStackTrace();
              }
              System.out.println(Thread.currentThread().getName()+"開始開會");
          }
      }, String.valueOf(finalI)).start();
  }

  

上述程式碼執行的結果為:

 

CyclicBarrier原來是這樣的

 

原始碼解析:

一、構造方法

 

CyclicBarrier原來是這樣的

 

有兩個構造方法,只有帶Runnable引數的構造方法才會在所有執行緒都到達等待點之後執行Runnable裡面的run方法。

CyclicBarrier(int parties) {
    this(parties, null);
}

CyclicBarrier(int parties, Runnable barrierAction) {
    if (parties <= 0) throw new IllegalArgumentException();
    this.parties = parties;
    this.count = parties;
    this.barrierCommand = barrierAction;
}

  

二、維護鎖狀態邏輯

其底層使用ReentrantLock+Condition進行鎖狀態的維護

1、維護鎖狀態
private final ReentrantLock lock = new ReentrantLock();
private final Condition trip = lock.newCondition();
2、執行緒組數 
private final int parties;
3、所有執行緒到達等待點後執行的Runnable
private final Runnable barrierCommand;
4、需要等待的執行緒數量    
private int count;
6、屏障點定義
private static class Generation {
    boolean broken = false;
}

  

具體看看其是如何實現等待邏輯的,執行緒等待需要呼叫await方法

public int await() {
        return dowait(false, 0L);
 }
    
public int await(long timeout, TimeUnit unit){
    return dowait(true, unit.toNanos(timeout));
}

  

最終呼叫的是dowait方法

private int dowait(boolean timed, long nanos){
        final ReentrantLock lock = this.lock;
        1、獲取鎖
        lock.lock();
        try {
            final Generation g = generation;
            if (g.broken)
                throw new BrokenBarrierException();
            2、如果執行緒中斷,重置等待執行緒數量並且喚醒當前等待的執行緒 
            if (Thread.interrupted()) {
                breakBarrier();
                throw new InterruptedException();
            }
            3、等待執行緒數減1
            int index = --count;
            4、當等待執行緒數為 時
            if (index == 0) {  // tripped
                boolean ranAction = false;
                try {
                    5、執行所有執行緒都到達等待點之後的Runnable 
                    final Runnable command = barrierCommand;
                    if (command != null)
                        command.run();
                    ranAction = true;
                    6、喚醒所有執行緒並生成下一代 
                    nextGeneration();
                    return 0;
                } finally {
                    if (!ranAction)
                        breakBarrier();
                }
            }
            7、如果等待執行緒數不為0
            for (;;) {
                try {
                    8、根據傳入的引數來決定是定時等待還是非定時等待
                    if (!timed)
                        trip.await();
                    else if (nanos > 0L)
                        nanos = trip.awaitNanos(nanos);
                } catch (InterruptedException ie) {
                    9、執行緒中斷之後喚醒所有執行緒並進入下一代
                    if (g == generation && ! g.broken) {
                        breakBarrier();
                        throw ie;
                    } else { 
                      Thread.currentThread().interrupt();
                    }
                }
                10、如果執行緒因為打翻屏障操作而被喚醒則丟擲異常
                if (g.broken)
                    throw new BrokenBarrierException();
                11、如果執行緒因為換代操作而被喚醒則返回計數器的值
                if (g != generation)
                    return index;
                12、如果執行緒因為時間到了而被喚醒則打翻柵欄並丟擲異常
                if (timed && nanos <= 0L) {
                    breakBarrier();
                    throw new TimeoutException();
                }
            }

        } finally {
            lock.unlock();
        }
    }

  

可以看到,是通過index欄位控制執行緒等待的,當index不為0的時候,執行緒統一會進行阻塞,直到index為0的時候,才會喚醒所有執行緒,這時候所有執行緒才會繼續往下執行。

三、重複使用

這個跟CountdownLatch不一樣的是,CountdownLatch是一次性的,而CycliBarrier是可以重複使用的,只需呼叫一下reset方法。

public void reset() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        1、破壞當前的屏障點並喚醒所有執行緒 
        breakBarrier();   
        2、生成下一代
        nextGeneration(); 
    } finally {
        lock.unlock();
    }
}


private void breakBarrier() {
    generation.broken = true;
    將等待執行緒數量重置
    count = parties;
    喚醒所有執行緒 
    trip.signalAll();
}

private void nextGeneration() {
    喚醒所有執行緒
    trip.signalAll();
    將等待執行緒數量重置
    count = parties;
    generation = new Generation();
}

  

上述就是對CycliBarrier的解析。

=======================================================

我是Liusy,一個喜歡健身的程式設計師。

相關文章