生產環境中,存在需要等待多個執行緒都達到某種狀態後,才繼續執行的情景。併發工具CyclicBarrier就能夠完成這種功能。本篇從原始碼方面,簡要分析CyclicBarrier的實現原理。
使用示例
public class CyclicBarrierTest { public static void main(String[] args) { //屏障,阻攔3個執行緒 CyclicBarrier cyclicBarrier = new CyclicBarrier(3); new Thread(new Runnable() { @Override public void run() { System.out.println("執行緒1正在執行"); try { // 等待 cyclicBarrier.await(); } catch (Exception e) { e.printStackTrace(); } System.out.println("執行緒1執行結束,時間: " + System.currentTimeMillis()); } }).start(); new Thread(new Runnable() { @Override public void run() { System.out.println("執行緒2正在執行"); try { // 等待 cyclicBarrier.await(); } catch (Exception e) { e.printStackTrace(); } System.out.println("執行緒2執行結束,時間: " + System.currentTimeMillis()); } }).start(); new Thread(new Runnable() { @Override public void run() { System.out.println("執行緒3正在執行"); try { //執行緒3阻塞2秒,測試效果 Thread.sleep(2000); // 等待 cyclicBarrier.await(); } catch (Exception e) { e.printStackTrace(); } System.out.println("執行緒3執行結束,時間: " + System.currentTimeMillis()); } }).start(); } }
執行結果如下:
執行緒1正在執行 執行緒2正在執行 執行緒3正在執行 執行緒1執行結束,時間: 1550324116837 執行緒3執行結束,時間: 1550324116837 執行緒2執行結束,時間: 1550324116837
可以看到執行緒1,2,3在同一個時間結束。
原始碼分析
主要成員:
private final ReentrantLock lock = new ReentrantLock(); private final Condition trip = lock.newCondition(); private int count;
CyclicBarrier主要藉助重入鎖ReentrantLock和Condition實現。count初始值等於CyclicBarrier例項化指明的等待執行緒數量,用於等待執行緒計數。
主要方法await()
public int await() throws InterruptedException, BrokenBarrierException { try { return dowait(false, 0L); } catch (TimeoutException toe) { throw new Error(toe); // cannot happen } } private int dowait(boolean timed, long nanos) throws InterruptedException, BrokenBarrierException, TimeoutException { final ReentrantLock lock = this.lock; lock.lock(); // 1 try { final Generation g = generation; if (g.broken) throw new BrokenBarrierException(); if (Thread.interrupted()) { breakBarrier(); throw new InterruptedException(); } int index = --count; // 2 if (index == 0) { // 3 boolean ranAction = false; try { final Runnable command = barrierCommand; if (command != null) command.run(); ranAction = true; nextGeneration(); // 4 return 0; } finally { if (!ranAction) breakBarrier(); // 5 } } // loop until tripped, broken, interrupted, or timed out for (;;) { try { if (!timed) trip.await(); // 6 else if (nanos > 0L) nanos = trip.awaitNanos(nanos); } 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(); // 7 } }
- 對當前物件加鎖
- 每個執行緒獲得鎖,執行這部分程式碼時,都把count – 1,記做index
- 如果index為0,執行第4步,代表CyclicBarrier屏障已經攔截了足夠數量(count)的執行緒,執行緒可以接著往下執行了。不為0,說明當前執行緒還沒有達到屏障CyclicBarrier攔截的數量,執行第6步
- 呼叫nextGeneration()方法,喚醒所有等待執行緒
- breakBarrier()確保一定能執行喚醒動作
- 呼叫Condition的await()方法,將當前執行緒放入等待佇列,釋放鎖
- 一定執行的釋放鎖動作。
nextGeneration()的程式碼如下:
private void nextGeneration() { // signal completion of last generation trip.signalAll(); // set up next generation count = parties; generation = new Generation(); }
使用Condition的signalAll()方法,喚醒全部等待執行緒
說完CyclicBarrier的原理之後,再對本篇的使用示例做一下描述:
- 執行緒1開始執行,呼叫await()方法,獲得鎖。此時count為3,count–,故count為2,index為2,呼叫Condition.await()方法,執行緒1進入等待佇列,釋放鎖
- 執行緒2開始執行,過程與第一步相同,只是count減為1
- 執行緒3開始執行,獲得鎖,count減為0,達到攔截數量,呼叫nextGeneration()方法喚醒全部執行緒,釋放自己持有的鎖
- 執行緒1,2都被喚醒,根據鎖競爭結果,依次執行完await()方法,最後釋放鎖
- 3個執行緒再往下執行自己的run()方法
異常分析:
假設呼叫cyclicBarrier.await()進行等待的執行緒數大於屏障CyclicBarrier例項化時宣告的攔截數,會發生什麼情況呢?
例如如下程式碼:
public static void main(String[] args) { //屏障,阻攔3個執行緒 CyclicBarrier cyclicBarrier = new CyclicBarrier(3); new Thread(new Runnable() { @Override public void run() { System.out.println("執行緒1正在執行"); try { // 等待 cyclicBarrier.await(); } catch (Exception e) { e.printStackTrace(); } System.out.println("執行緒1執行結束,時間: " + System.currentTimeMillis()); } }).start(); new Thread(new Runnable() { @Override public void run() { System.out.println("執行緒2正在執行"); try { // 等待 cyclicBarrier.await(); } catch (Exception e) { e.printStackTrace(); } System.out.println("執行緒2執行結束,時間: " + System.currentTimeMillis()); } }).start(); new Thread(new Runnable() { @Override public void run() { System.out.println("執行緒3正在執行"); try { //執行緒3阻塞2秒,測試效果 // Thread.sleep(2000); // 等待 cyclicBarrier.await(); } catch (Exception e) { e.printStackTrace(); } System.out.println("執行緒3執行結束,時間: " + System.currentTimeMillis()); } }).start(); new Thread(new Runnable() { @Override public void run() { System.out.println("執行緒4正在執行"); try { // 等待 cyclicBarrier.await(); } catch (Exception e) { e.printStackTrace(); } System.out.println("執行緒4執行結束,時間: " + System.currentTimeMillis()); } }).start(); }
呼叫cyclicBarrier.await()方法等待的執行緒一共4個,CyclicBarrier宣告只攔截3個。
上述用例將導致一個執行緒得不到執行,處於等待狀態。
分析一下原因:
在CyclicBarrier的dowait()方法215行(JDK1.8)中,只有在index == 0,也就是CyclicBarrier攔截到了例項化時指明的執行緒數量時,才會呼叫Condition.signalAll()喚醒等待執行緒。所以在第4個執行緒進入此方法時,index減為-1,會呼叫Condition.await()開始等待。這樣就沒有執行緒能執行喚醒邏輯了,它將一直處於等待狀態。