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的更多相关文章

  1. 《java.util.concurrent 包源码阅读》 结束语

    <java.util.concurrent 包源码阅读>系列文章已经全部写完了.开始的几篇文章是根据自己的读书笔记整理出来的(当时只阅读了部分的源代码),后面的大部分都是一边读源代码,一边 ...

  2. 《java.util.concurrent 包源码阅读》13 线程池系列之ThreadPoolExecutor 第三部分

    这一部分来说说线程池如何进行状态控制,即线程池的开启和关闭. 先来说说线程池的开启,这部分来看ThreadPoolExecutor构造方法: public ThreadPoolExecutor(int ...

  3. 《java.util.concurrent 包源码阅读》02 关于java.util.concurrent.atomic包

    Aomic数据类型有四种类型:AomicBoolean, AomicInteger, AomicLong, 和AomicReferrence(针对Object的)以及它们的数组类型, 还有一个特殊的A ...

  4. 《java.util.concurrent 包源码阅读》04 ConcurrentMap

    Java集合框架中的Map类型的数据结构是非线程安全,在多线程环境中使用时需要手动进行线程同步.因此在java.util.concurrent包中提供了一个线程安全版本的Map类型数据结构:Concu ...

  5. 《java.util.concurrent 包源码阅读》17 信号量 Semaphore

    学过操作系统的朋友都知道信号量,在java.util.concurrent包中也有一个关于信号量的实现:Semaphore. 从代码实现的角度来说,信号量与锁很类似,可以看成是一个有限的共享锁,即只能 ...

  6. 《java.util.concurrent 包源码阅读》06 ArrayBlockingQueue

    对于BlockingQueue的具体实现,主要关注的有两点:线程安全的实现和阻塞操作的实现.所以分析ArrayBlockingQueue也是基于这两点. 对于线程安全来说,所有的添加元素的方法和拿走元 ...

  7. 《java.util.concurrent 包源码阅读》09 线程池系列之介绍篇

    concurrent包中Executor接口的主要类的关系图如下: Executor接口非常单一,就是执行一个Runnable的命令. public interface Executor { void ...

  8. 《java.util.concurrent 包源码阅读》05 BlockingQueue

    想必大家都很熟悉生产者-消费者队列,生产者负责添加元素到队列,如果队列已满则会进入阻塞状态直到有消费者拿走元素.相反,消费者负责从队列中拿走元素,如果队列为空则会进入阻塞状态直到有生产者添加元素到队列 ...

  9. 《java.util.concurrent 包源码阅读》10 线程池系列之AbstractExecutorService

    AbstractExecutorService对ExecutorService的执行任务类型的方法提供了一个默认实现.这些方法包括submit,invokeAny和InvokeAll. 注意的是来自E ...

随机推荐

  1. ST40 自制 JTAG 适配器

    // 文章首发于 https://zhuanlan.zhihu.com/p/28762429 // 但是不知道为什么搜索引擎还没有收录,便在博客再次发布. 0. 引言 意法半导体生产的 SH4 架构的 ...

  2. WordPress 4.8 安装配置教程 (基于 centos 7.3, php 7.0, mysql 5.7.19, nginx 1.12.1)

    最近想要整个 blog,记录自己工作.学习中的点滴.Wordpress 自然是首选,因为内容才是关键,所以也就不怕别人说太 low.网上大部份都是讲 wordpress 配合 apache 的安装教程 ...

  3. 关于Java和JavaScript对字符串截取处理的总结

    在JavaWeb开发中,经常需要对字符串进行处理,包括Java语言和JS语言,总是容易弄混淆,这里简单对比一下两种语言对于字符串截取方法. 一.先看Java public class StringDe ...

  4. C 返回函数与闭包的考虑

    #include <stdio.h> typedef int (*fun)(); fun closure(int i) { int squ() { return i*i; } return ...

  5. js页面事件方法

    1. 监听页面加载事件: window.onload = function (){ alert('页面加载成功'); } 2.监听页面大小改变事件: window.onresize = functio ...

  6. zookeeper启动后的注意事项

    在各节点中运行zkServer.sh start后 1.首先看进程是否存在 QuorumPeerMain. 2.查看zookeeper的运行状态 zkServer.sh status,会出现一个lea ...

  7. Leetcode题解(30)

    98. Validate Binary Search Tree 题目 分析:BST按照中序遍历之后所得到的序列是一个递增序列,因此可以按照这个思路,先中序遍历,保存好遍历的结果,然后在遍历一遍这个序列 ...

  8. A strange lift

    Problem Description There is a strange lift.The lift can stop can at every floor as you want, and th ...

  9. eclipse禁用svg文件Validation

    1.打开window>preferences>validation找到xml validator 2.点击xml validator最右侧的按钮打开xml校验规则窗口,选中exclude ...

  10. 解决网络不可用--Using_Service_Workers

    Using_Service_Workers:https://developer.mozilla.org/zh-CN/docs/Web/API/Service_Worker_API/Using_Serv ...