上一篇聊了一下Semaphore訊號燈的用法及原始碼,這一篇來聊一下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(); }
上述程式碼執行的結果為:
原始碼解析:
一、構造方法
有兩個構造方法,只有帶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,一個喜歡健身的程式設計師。