首页 > 代码库 > 《java.util.concurrent 包源码阅读》21 CyclicBarrier和CountDownLatch
《java.util.concurrent 包源码阅读》21 CyclicBarrier和CountDownLatch
CyclicBarrier是一个用于线程同步的辅助类,它允许一组线程等待彼此,直到所有线程都到达集合点,然后执行某个设定的任务。
现实中有个很好的例子来形容:几个人约定了某个地方集中,然后一起出发去旅行。每个参与的人就是一个线程,CyclicBarrier就是那个集合点,所有人到了之后,就一起出发。
CyclicBarrier的构造函数有两个:
// parties是参与等待的线程的数量,barrierAction是所有线程达到集合点之后要做的动作public CyclicBarrier(int parties, Runnable barrierAction);// 达到集合点之后不执行操作的构造函数public CyclicBarrier(int parties)
需要说明的是,CyclicBarrier只是记录线程的数目,CyclicBarrier是不创建任何线程的。线程是通过调用CyclicBarrier的await方法来等待其他线程,如果调用await方法的线程数目达到了预设值,也就是上面构造方法中的parties,CyclicBarrier就会开始执行barrierAction。
因此我们来看CyclicBarrier的核心方法dowait,也就是await方法调用的私有方法:
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) throw new BrokenBarrierException(); if (Thread.interrupted()) { breakBarrier(); throw new InterruptedException(); } // count就是预设的parties,count减1的值表示还剩余几个 // 线程没有达到该集合点 int index = --count; // index为0表示所有的线程都已经达到集合点,这时 // 占用最后一个线程,执行运行设定的任务 if (index == 0) { boolean ranAction = false; try { final Runnable command = barrierCommand; if (command != null) command.run(); ranAction = true; // 唤醒其他等待的线程, // 更新generation以便下一次运行 nextGeneration(); return 0; } finally { // 如果运行任务时发生异常,设置状态为broken // 并且唤醒其他等待的线程 if (!ranAction) breakBarrier(); } } // 还有线程没有调用await,进入循环等待直到其他线程 // 达到集合点或者等待超时 for (;;) { try { // 如果没有设置超时,进行无超时的等待 if (!timed) trip.await(); // 有超时设置,进行有超时的等待 else if (nanos > 0L) nanos = trip.awaitNanos(nanos); } catch (InterruptedException ie) { // generation如果没有被更新表示还是当前的运行
// (generation被更新表示集合完毕并且任务成功), // 在状态没有被设置为broken状态的情况下,遇到线程 // 中断异常表示当前线程等待失败,需要设置为broken // 状态,并且抛出中断异常 if (g == generation && ! g.broken) { breakBarrier(); throw ie; } else { // else对应的条件为:g != generation || g.broken // 表示要么generation已经被更新意味着所有线程已经到达
// 集合点并且任务执行成功,要么就是是broken状态意味着
// 任务执行失败,无论哪种情况所有线程已经达到集合点,当
// 前线程要结束等待了,发生了中断异常,需要中断当前线程
// 表示遇到了中断异常。 Thread.currentThread().interrupt(); } } // 如果发现当前状态为broken,抛出异常 if (g.broken) throw new BrokenBarrierException(); // generation被更新表示所有线程都已经达到集合点 // 并且预设任务已经完成,返回该线程进入等待顺序号 if (g != generation) return index; // 等待超时,设置为broken状态并且抛出超时异常 if (timed && nanos <= 0L) { breakBarrier(); throw new TimeoutException(); } } } finally { lock.unlock(); } }
1. 任何一个线程等待时发生异常,CyclicBarrier都将被设置为broken状态,运行都会失败
2. 每次运行成功之后CyclicBarrier都会清理运行状态,这样CyclicBarrier可以重新使用
3. 对于设置了超时的等待,在发生超时的时候会引起CyclicBarrier的broken
说完了CyclicBarrier,再来说说CountDownLatch。
CountDownLatch同样也是一个线程同步的辅助类,同样适用上面的集合点的场景来解释,但是运行模式完全不同。
CyclicBarrier是参与的所有的线程彼此等待,CountDownLatch则不同,CountDownLatch有一个导游线程在等待,每个线程报到一下即可无须等待,等到导游线程发现所有人都已经报到了,就结束了自己的等待。
CountDownLatch的构造方法允许指定参与的线程数量:
public CountDownLatch(int count)
参与线程使用countDown表示报到:
public void countDown() { sync.releaseShared(1); }
看到releaseShared很容易使人联想到共享锁,那么试着用共享锁的运行模式来解释就简单得多了:
和信号量的实现类似,CountDownLatch内置一下有限的共享锁。
每个参与线程拥有一把共享锁,调用countDown就等于是释放了自己的共享锁,导游线程await等于一下子要拿回所有的共享锁。那么基于AbstractQueuedSynchronizer类来实现就很简单了:
public void await() throws InterruptedException { sync.acquireSharedInterruptibly(1); } public boolean await(long timeout, TimeUnit unit) throws InterruptedException { return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout)); }
在await时注意到数量是1,其实这个参数对于CountDownLatch实现的Sync类(AbstractQueuedSynchronizer的子类)来说是不起作用的,因为需要保证await获取共享锁时必须拿到所有的共享锁,这个参数也就变得没有意义了。看一下Sync的tryAcquireShared方法就明白了:
protected int tryAcquireShared(int acquires) { // 和信号量Semaphore的实现一样,使用state来存储count, // 每次释放共享锁就把state减1,state为0表示所有的共享 // 锁已经被释放。注意:这里的acquires参数不起作用 return (getState() == 0) ? 1 : -1; }
因此Sync的tryReleaseShared就是更新state(每次state减1):
protected boolean tryReleaseShared(int releases) { // 每次state减1,当state为0,返回false表示所有的共享锁都已经释放 for (;;) { int c = getState(); if (c == 0) return false; int nextc = c-1; if (compareAndSetState(c, nextc)) return nextc == 0; } }
CyclicBarrier和CountDownLatch本质上来说都是多个线程同步的辅助工具,前者可以看成分布式的,后者可以看出是主从式。
《java.util.concurrent 包源码阅读》21 CyclicBarrier和CountDownLatch