生产环境中,存在需要等待多个线程都达到某种状态后,才继续运行的情景。并发工具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()开始等待。这样就没有线程能执行唤醒逻辑了,它将一直处于等待状态。