JAVA并发编程JUC之CyclicBarrier源码分析
CyclicBarrier
基本解释:
顾名思义循环栅栏,用于将多个线程等待在某个位置,所有线程都准备好之后,全部同时执行;它的一个特点就是可以循环使用(Cyclic);
有点类似于跑步比赛,大家等待在起跑点,一声枪响,所有参赛者都开始跑。
使用举例:
class Solver { final int N; final float[][] data; final CyclicBarrier barrier; class Worker implements Runnable { int myRow; Worker(int row) { myRow = row; } public void run() { while (!done()) { processRow(myRow); try { barrier.await(); } catch (InterruptedException ex) { return; } catch (BrokenBarrierException ex) { return; } } } } public Solver(float[][] matrix) { data = matrix; N = matrix.length; Runnable barrierAction = new Runnable() { public void run() { mergeRows(...); }}; barrier = new CyclicBarrier(N, barrierAction); List<Thread> threads = new ArrayList<Thread>(N); for (int i = 0; i < N; i++) { Thread thread = new Thread(new Worker(i)); threads.add(thread); thread.start(); } // wait until done for (Thread thread : threads) thread.join(); } }
源码分析:
主要成员变量包含:
// 用于创建Condition的锁
private final ReentrantLock lock = new ReentrantLock();
// 所有线程都等待在此condition上 private final Condition trip = lock.newCondition();
// 记录此barrier有多少个参与者
private final int parties;
// 保存打开栅栏时,采取的动作 private final Runnable barrierCommand;
// 保存此轮 private Generation generation = new Generation();
// 记录还需多少个线程到达此barrier才能打开, 已等待数 = parties - count; private int count;
其中主要的方法包括:
// 创建一个CyclicBarrier,其中parties为多少个线程等待在这个barrier上之后,打开栅栏,进入下一次迭代
// barrierAction表示在打开栅栏之前需要做的事情,由最后一个到达栅栏的线程执行
public CyclicBarrier(int parties, Runnable barrierAction);
// 同上一个,栅栏打开的时候不需要做其他事情,只需要释放线程就可以
public CyclicBarrier(int parties);
// 将一个线程等待在此barrier上
public int await() throws InterruptedException, BrokenBarrierException;
// 将一个线程等待在此barrier上,并且最多等待timeout的时间,若时间到则退出等待,并且此线程会打开栅栏
public int await(long timeout, TimeUnit unit) throws InterruptedException, BrokenBarrierException, TimeoutException;
// 查看barrier是否已经打开
public boolean isBroken();
// 重置栅栏,会将已经等待在此栅栏上的线程全都释放掉
public void reset();
// 获得等待在此栅栏上的线程数
public int getNumberWaiting();
最主要的方法为两个await方法,这两个await方法同时调用了dowait方法,dowait负责具体的阻塞线程。
下面分析dowait方法:
private int dowait(boolean timed, long nanos) throws InterruptedException, BrokenBarrierException, TimeoutException { final ReentrantLock lock = this.lock; lock.lock(); try { final Generation g = generation; if (g.broken) // barrier已经被打开了,不能继续使用,除非重置(reset)一下 throw new BrokenBarrierException(); if (Thread.interrupted()) { // 此线程被中断了,破坏栅栏使得其他线程可以执行,此线程则被抛出InterruptedException breakBarrier(); throw new InterruptedException(); } int index = --count; // 更新count,count为剩余等待数,index则表示我是倒数第几个来打此barrier上的,0表示最后一个,parties-1表示最后一个 if (index == 0) { // tripped 所有线程都已经达到 boolean ranAction = false; try { final Runnable command = barrierCommand; if (command != null) command.run(); // 最后一个线程执行打开栅栏时需要的动作 ranAction = true; nextGeneration(); // 进入下一个周期 return 0; } finally { // 如果在上述期间发生了任何异常,则会破坏barrier,使得等待在此barrier上的线程得以执行 // 这里我觉得主要是为了防止command.run中出现异常信息时的一种恢复机制,毕竟其他人写的这个command并不可靠 if (!ranAction) breakBarrier(); } } // loop until tripped, broken, interrupted, or timed out // 循环等待,直至触发,打开,中断或者超时 for (;;) { try { if (!timed) // 未指定超时时间 trip.await(); // 等待在condition上 else if (nanos > 0L) nanos = trip.awaitNanos(nanos); // 等待至超时,或被唤醒 } catch (InterruptedException ie) { // 被中断了 if (g == generation && ! g.broken) { // 若当前barrier还是进入等待时的那一轮,并且没有被打开 breakBarrier(); // 打开barrier 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. // 此barrier已经不是我等待之前的那个barrier了,我现在要中断我自己,这里有点迷糊,不清楚为什么不直接抛出 ie Thread.currentThread().interrupt(); } } // 我被唤醒了,看看barrier是否broken掉了,如果是被broken掉的,说明出现了一些异常 if (g.broken) throw new BrokenBarrierException(); // 我被唤醒了,但是此barrier已经不是我等待时的那个barrier了,我不管它,继续做我自己的事情 if (g != generation) return index; // 我超时了,不能继续待在此barrier上了,那我要破坏此barrier以便让其他线程知道 if (timed && nanos <= 0L) { breakBarrier(); throw new TimeoutException(); } } } finally { lock.unlock(); } }
breakBarrier和reset也是频繁使用的方法:
private void breakBarrier() { generation.broken = true; // 标记barrier已被破坏 count = parties; // 还原count trip.signalAll(); // 唤醒已经等待在此barrier上的线程 }
public void reset() { final ReentrantLock lock = this.lock; lock.lock(); try { breakBarrier(); // break the current generation 破坏上一轮 nextGeneration(); // start a new generation 开始下一轮 } finally { lock.unlock(); } }
private void nextGeneration() { // signal completion of last generation trip.signalAll(); // set up next generation count = parties; generation = new Generation(); }