1. 前沿

从上一节的CountDownLatch的学习,我们发现其只能使用一次,当state递减为0后,就没有用了,需要重新新建一个计数器。那么我们有没有可以复用的计数器呢?当然,JUC包给我们提供了CyclicBarrier回环屏障来实现计数器的复用。

2. 概念讲解

  何为回环:当所有等待线程执行完成后,重置CyclicBarrier的状态,使得它能被复用。

  何为屏障:线程调用await方法后就会被阻塞,这个阻塞点就叫屏障点,等所有线程都调用await方法后,线程们就会突破屏障,继续往下运行。

3. 案例

4. 源码分析

  • 构造函数
// 一个入参的构造函数
public CyclicBarrier(int parties) {
// 调用还是两个入参的构造函数
this(parties, null);
} public CyclicBarrier(int parties, Runnable barrierAction) {
if (parties <= 0) throw new IllegalArgumentException();
// 将入参计数器值赋给count和线程数parties
this.parties = parties;
this.count = parties;
// 计数器count=0时,执行下面barrierCommand的功能
this.barrierCommand = barrierAction;
}
  • await()
public int await() throws InterruptedException, BrokenBarrierException {
try {
// 实际调用的dowait方法
return dowait(false, 0L);
} catch (TimeoutException toe) {
throw new Error(toe); // cannot happen
}
}
private int dowait(boolean timed, long nanos)
throws InterruptedException, BrokenBarrierException,
TimeoutException {
     // 回环屏障底层是用ReentrantLock独占锁实现,其底层还是基于AQS实现
final ReentrantLock lock = this.lock;
lock.lock();
try {
final Generation g = generation;
       // 如果回环屏障是broken=true状态,抛出异常。因为dowait之前broken一定是false的
if (g.broken)
throw new BrokenBarrierException();
// 如果线程interrupted了,那么打破屏障,代码往下执行
if (Thread.interrupted()) {
breakBarrier();
throw new InterruptedException();
}
        // 计数器递减1
int index = --count;
// 当计数器为0时,打破屏障
if (index == 0) { // tripped
boolean ranAction = false;
try {
// 我们代码中调用两个参数的构造函数时,传入的一个任务
final Runnable command = barrierCommand;
            // 如果任务不为空,那么在回环屏障打破的时候,执行我们自定义的任务。  
if (command != null)
command.run();
ranAction = true;
nextGeneration();
return 0;
} finally {
// 如果try方法出现异常,没有正常将ranAction设置为true,那么finally里强行打破屏障
if (!ranAction)
breakBarrier();
}
}
       // 以下是设置了超时时间的dowait方法实现,当等待时间超过了超时时间时,屏障等待也会结束
for (;;) {
try {
if (!timed)
trip.await();
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();
}
}
// 打破屏障的方法
private void breakBarrier() {
     // 设置打破状态为true
generation.broken = true;
     // 将线程数重新赋给计数器count,这是保证屏障复用的关键。
count = parties;
     // 唤醒其他所有因为调用await方法而发生阻塞的线程
trip.signalAll();
}

5. 总结

  当我们创建回环屏障对象时,传入的计数器值M,前M-1个线程调用await方法时,获得独占锁,串行话执行dowait方法,都将count递减1,并且将M-1个线程加入到trip的条件队列中去。当最后一个线程执行到await方法时,最终将count置为了0,同时唤醒trip条件队列中所有被阻塞的线程,使得所有的M个线程继续往下执行。

CyclicBarrier回环屏障深度解析的更多相关文章

  1. 回环屏障CyclicBarrier

    上一篇说的CountDownLatch是一个计数器,类似线程的join方法,但是有一个缺陷,就是当计数器的值到达0之后,再调用CountDownLatch的await和countDown方法就会立刻返 ...

  2. thread_CyclicBarrier回环栅栏

    CyclicBarrier回环栅栏,字面意思是可循环使用(Cyclic)的屏障(Barrier).通过它可以实现让一组线程等待至某个状态之后再全部同时执行. 它要做的事情是,让一组线程到达一个屏障(也 ...

  3. 回环栅栏CyclicBarrier

    通过它可以实现让一组线程等待至某个状态之后再全部同时执行.叫做回环是因为当所有等待线程都被释放以后,CyclicBarrier可以被重用.我们暂且把这个状态就叫做barrier,当调用await()方 ...

  4. 并发编程-concurrent指南-回环栅栏CyclicBarrier

    字面意思回环栅栏,通过它可以实现让一组线程等待至某个状态之后再全部同时执行. java.util.concurrent.CyclicBarrier 类是一种同步机制,它能够对处理一些算法的线程实现同步 ...

  5. 一个基于深度学习回环检测模块的简单双目 SLAM 系统

    转载请注明出处,谢谢 原创作者:Mingrui 原创链接:https://www.cnblogs.com/MingruiYu/p/12634631.html 写在前面 最近在搞本科毕设,关于基于深度学 ...

  6. ORB-SLAM(六)回环检测

    上一篇提到,无论在单目.双目还是RGBD中,追踪得到的位姿都是有误差的.随着路径的不断延伸,前面帧的误差会一直传递到后面去,导致最后一帧的位姿在世界坐标系里的误差有可能非常大.除了利用优化方法在局部和 ...

  7. Kafka深度解析

    本文转发自Jason’s Blog,原文链接 http://www.jasongj.com/2015/01/02/Kafka深度解析 背景介绍 Kafka简介 Kafka是一种分布式的,基于发布/订阅 ...

  8. mybatis 3.x源码深度解析与最佳实践(最完整原创)

    mybatis 3.x源码深度解析与最佳实践 1 环境准备 1.1 mybatis介绍以及框架源码的学习目标 1.2 本系列源码解析的方式 1.3 环境搭建 1.4 从Hello World开始 2 ...

  9. Kafka深度解析(如何在producer中指定partition)(转)

    原文链接:Kafka深度解析 背景介绍 Kafka简介 Kafka是一种分布式的,基于发布/订阅的消息系统.主要设计目标如下: 以时间复杂度为O(1)的方式提供消息持久化能力,即使对TB级以上数据也能 ...

随机推荐

  1. 牛客练习赛66 C公因子 题解(区间gcd)

    题目链接 题目大意 给你一个长为n的数组,给所有数组元素加上一个非负整数x,使得这个数组的所有元素的gcd最大 题目思路 这主要是设计到一个多个数gcd的性质 gcd(a,b,c,d.....)=gc ...

  2. ESP8266 ESP-01制作B站粉丝计数器 | Arduino IDE开发ESP8266 | ESP8266存储读取用户数据

    1.实物图 2.原理图 3.Arduino程序 在程序中修改自己的B站UID和自己家的WiFi名称及密码即可. /*B站粉丝计数器*/ /******************************* ...

  3. 数据库default null字段用基本类型映射,改成包装类型后缓存中旧数据反序列化失败

    rt,spring Temp不知道用的什么反序列化,int不能反序列化为Integer,后实验hissing是可以的int->Integer  Integer(不为null)->int均可

  4. pixi.js持续渲染页面

    Pixi是一个超快的2D渲染引擎,通过Javascript和Html技术创建动画或管理交互式图像,从而制作游戏或应用. 项目地址:https://github.com/pixijs/pixi.js A ...

  5. charles功能(二)修改response请求参数

    1.接口处 鼠标右击,选择breakpoints(允许本接口使用breakpionts功能) 2.开始设置断点值 4.重新请求接口(charles的界面变为可编辑状态),修改请求参数,执行请求 5.最 ...

  6. 强大的拉姆表达式转Sql 类库 - SqlSugar 隐藏功能之Lambda

    使用场景 1.Lambda to sql 一直是ORM中最难的功能之一,如果有现成的解析库那么自已写一个ORM难度将大大降低 2.通过Lambda作为KEY进行缓存操作,特别是仓储模式想要拿到表达式进 ...

  7. PyQt(Python+Qt)学习随笔:QListWidget的访问当前项的currentItem和setCurrentItem方法

    老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 currentItem方法返回列表部件当前选择的项,setCurrentItem方法用于设置当前项. ...

  8. 使用k8s部署springboot+redis简单应用

    准备 本文将使用k8s部署一个springboot+redis应用,由于是示例,所以功能比较简单,只有设置值和获取值两个api. (1)设置值 (2)获取值 构建Web应用 (1)创建一个spring ...

  9. Asp.net core中RedisMQ的简单应用

    最近一个外部的项目,使用到了消息队列,本来是用rabbitmq实现的,但是由于是部署到别人家的服务器上,想尽量简化一些,项目中本来也要接入了redis缓存,就尝试使用redis来实现简单的消息队列. ...

  10. 一文看懂 Kubernetes 服务发现: Service

    Service 简介   K8s 中提供微服务的实体是 Pod,Pod 在创建时 docker engine 会为 pod 分配 ip,"外部"流量通过访问该 ip 获取微服务.但 ...