回环屏障CyclicBarrier
上一篇说的CountDownLatch是一个计数器,类似线程的join方法,但是有一个缺陷,就是当计数器的值到达0之后,再调用CountDownLatch的await和countDown方法就会立刻返回,就没有作用了,那么反正是一个计数器,为什么不能重复使用呢?于是就出现了这篇说的CyclicBarrier,它的状态可以被重用;
一.简单例子
用法其实和CountDownLatch差不多,也就是一个计数器,当计数器的值变为0之后,就会把阻塞的线程唤醒:
- package com.example.demo.study;
- import java.util.concurrent.CyclicBarrier;
- import java.util.concurrent.ExecutorService;
- import java.util.concurrent.Executors;
- public class Study0216 {
- // 注意这里的构造器,第一个参数表示计数器初始值
- // 第二个参数表示当计数器的值变为0的时候就触发的任务
- static CyclicBarrier cyclicBarrier = new CyclicBarrier(2, () -> {
- System.out.println("cyclicBarrier task ");
- });
- public static void main(String[] args) {
- // 新建两个线程的线程池
- ExecutorService pool = Executors.newFixedThreadPool(2);
- // 线程1放入线程池中
- pool.submit(() -> {
- try {
- System.out.println("Thread1----await-begin");
- cyclicBarrier.await();
- System.out.println("Thread1----await-end");
- } catch (Exception e) {
- e.printStackTrace();
- }
- });
- // 线程2放到线程池中
- pool.submit(() -> {
- try {
- System.out.println("Thread2----await-begin");
- cyclicBarrier.await();
- System.out.println("Thread2----await-end");
- } catch (Exception e) {
- e.printStackTrace();
- }
- });
- // 关闭线程池,此时还在执行的任务会继续执行
- pool.shutdown();
- }
- }
我们再看看CyclicBarrier的复用性,这里比如有一个任务,有三部分组成,分别是A,B,C,然后创建两个线程去执行这个任务,必须要等到两个线程都执行完成A部分,然后才能开始执行B,只有两个线程都执行完成B部分,才能执行C:
- package com.example.demo.study;
- import java.util.concurrent.CyclicBarrier;
- import java.util.concurrent.ExecutorService;
- import java.util.concurrent.Executors;
- public class Study0216 {
- // 这里的构造器,只有一个参数,表示计数器初始值
- static CyclicBarrier cyclicBarrier = new CyclicBarrier(2);
- public static void main(String[] args) {
- // 新建两个线程的线程池
- ExecutorService pool = Executors.newFixedThreadPool(2);
- // 线程1放入线程池中
- pool.submit(() -> {
- try {
- System.out.println("Thread1----stepA-start");
- cyclicBarrier.await();
- System.out.println("Thread1----stepB-start");
- cyclicBarrier.await();
- System.out.println("Thread1----stepC-start");
- } catch (Exception e) {
- e.printStackTrace();
- }
- });
- // 线程2放到线程池中
- pool.submit(() -> {
- try {
- System.out.println("Thread2----stepA-start");
- cyclicBarrier.await();
- System.out.println("Thread2----stepB-start");
- cyclicBarrier.await();
- System.out.println("Thread2----stepC-start");
- } catch (Exception e) {
- e.printStackTrace();
- }
- });
- // 关闭线程池,此时还在执行的任务会继续执行
- pool.shutdown();
- }
- }
二.基本原理
我们看看一些重要属性:
- public class CyclicBarrier {
- //这个内部类只有一个boolean值
- private static class Generation {
- boolean broken = false;
- }
- //独占锁
- private final ReentrantLock lock = new ReentrantLock();
- //条件变量
- private final Condition trip = lock.newCondition();
- //保存线程的总数
- private final int parties;
- //这是一个任务,通过构造器传递一个任务,当计数器变为0之后,就可以执行这个任务
- private final Runnable barrierCommand;
- //这类内部之后一个boolean的值,表示屏障是否被打破
- private Generation generation = new Generation();
- //计数器
- private int count;
- }
构造器:
- //我们的构造器初始值设置的是parties
- public CyclicBarrier(int parties) {
- this(parties, null);
- }
- //注意,这里开始的时候是count等于parties
- //为什么要有两个变量呢?我们每次调用await方法的时候count减一,当count的值变为0之后,怎么又还原成初始值呢?
- //直接就把parties的值赋值给count就行了呀,简单吧!
- public CyclicBarrier(int parties, Runnable barrierAction) {
- if (parties <= 0) throw new IllegalArgumentException();
- this.parties = parties;
- this.count = parties;
- 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
- }
- }
- //假设count等于3,有三个线程都在调用这个方法,默认超时时间为0,那么首每次都只有一个线程可以获取锁,将count减一,不为0
- //就会到下面的for循环中扔到条件队列中挂起;直到第三个线程调用这个dowait方法,count减一等于0,那么当前线程执行任务之后,
- //就会唤醒条件变量中阻塞的线程,并重置count为初始值3
- private int dowait(boolean timed, long nanos)throws InterruptedException, BrokenBarrierException, TimeoutException {
- //获取锁
- final ReentrantLock lock = this.lock;
- lock.lock();
- try {
- //g中只有一个boolean值
- final Generation g = generation;
- //如果g中的值为true的时候,抛错
- if (g.broken)
- throw new BrokenBarrierException();
- //如果当前线程中断,就抛错
- if (Thread.interrupted()) {
- breakBarrier();
- throw new InterruptedException();
- }
- //count减一,再赋值给index
- int index = --count;
- //如果index等于0的时候,说明所有的线程已经到屏障点了,就可以
- if (index == 0) { // tripped
- boolean ranAction = false;
- try {
- //执行当前线程的任务
- final Runnable command = barrierCommand;
- if (command != null)
- command.run();
- ranAction = true;
- //唤醒其他因为调用了await方法阻塞的线程
- nextGeneration();
- return 0;
- } finally {
- if (!ranAction)
- breakBarrier();
- }
- }
- //能到这里来,说明是count不等于0,也就是还有的线程没有到屏障点
- for (;;) {
- try {
- //wait方法有两种情况,一种是设置超时时间,一种是不设置超时时间
- //这里就是对超时时间进行的一个判断,如果设置的超时时间为0,则会在条件队列中无限的等待下去,直到被唤醒
- //设置了超时时间,那就等待该时间
- if (!timed)
- trip.await();
- else if (nanos > 0L)
- nanos = trip.awaitNanos(nanos);
- } catch (InterruptedException ie) {
- if (g == generation && ! g.broken) {
- breakBarrier();
- throw ie;
- } else {
- 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();
- }
- }
- //唤醒其他因为调用了await方法阻塞的线程
- private void nextGeneration() {
- //唤醒条件变量中所有线程
- trip.signalAll();
- //重置count的值
- count = parties;
- generation = new Generation();
- }
- private void breakBarrier() {
- generation.broken = true;
- //重置count为初始值parties
- count = parties;
- //唤醒条件队列中的所有线程
- trip.signalAll();
- }
回环屏障CyclicBarrier的更多相关文章
- CyclicBarrier回环屏障深度解析
1. 前沿 从上一节的CountDownLatch的学习,我们发现其只能使用一次,当state递减为0后,就没有用了,需要重新新建一个计数器.那么我们有没有可以复用的计数器呢?当然,JUC包给我们提供 ...
- 回环栅栏CyclicBarrier
通过它可以实现让一组线程等待至某个状态之后再全部同时执行.叫做回环是因为当所有等待线程都被释放以后,CyclicBarrier可以被重用.我们暂且把这个状态就叫做barrier,当调用await()方 ...
- 并发编程-concurrent指南-回环栅栏CyclicBarrier
字面意思回环栅栏,通过它可以实现让一组线程等待至某个状态之后再全部同时执行. java.util.concurrent.CyclicBarrier 类是一种同步机制,它能够对处理一些算法的线程实现同步 ...
- thread_CyclicBarrier回环栅栏
CyclicBarrier回环栅栏,字面意思是可循环使用(Cyclic)的屏障(Barrier).通过它可以实现让一组线程等待至某个状态之后再全部同时执行. 它要做的事情是,让一组线程到达一个屏障(也 ...
- ORB-SLAM(六)回环检测
上一篇提到,无论在单目.双目还是RGBD中,追踪得到的位姿都是有误差的.随着路径的不断延伸,前面帧的误差会一直传递到后面去,导致最后一帧的位姿在世界坐标系里的误差有可能非常大.除了利用优化方法在局部和 ...
- SharePoint回环检查(Loopback Check)相关问题
Loopback Check(回环检查)本来不是一个SharePoint问题,是Windows Server为了增强自身安全性在Server 2003 SP1后引入的一个功能, 在近几个月中导致了一系 ...
- 关于STM32 CAN回环可用,正常不可用情况分析
1.回环下应该与GPIO无关 2.GPIO是否初始化正确,时钟启用 3.是否复用,AFIO时钟是否启用 4.回环下是否有CAN_Tx应该有输出 5.终端电阻是否有 6.CAN收发器电路电压是否正常 7 ...
- linux回环网卡驱动设计
回环网卡驱动 1.回环网卡和普通网卡的区别是他是虚拟的不是实际的物理网卡,它相当于把普通网卡的发送端和接收端短接在一起. 2.在内核源代码里的回环网卡程序(drivers/net/loopback.c ...
- VMware配置回环地址用于测试
我们在开发过程中,很可能需要一台服务器用于测试,在这种环境下,我们很可能需要用到vmware来构建这样的开发环境.但如果当前处在一个离线,或是不在网内的环境下,我们所搭建的环境有可能无法 ...
随机推荐
- mysql 主主备份
1.1.主主备份原理. 主主备份实际上是互为主从,主要是为了去缓解写入压力. 1.2.环境准备 两台机器ip分别为 100.100.100.105 (主1) 100.100.100.106(主2) 安 ...
- SpringBoot图文教程6—SpringBoot中过滤器的使用
有天上飞的概念,就要有落地的实现 概念十遍不如代码一遍,朋友,希望你把文中所有的代码案例都敲一遍 先赞后看,养成习惯 SpringBoot 图文系列教程技术大纲 鹿老师的Java笔记 SpringBo ...
- python数据类型(第一弹)
作为一门计算机编程语言,python与其它语言一样,设有若干种数据类型,准确掌握各种数据类型的常用方法是精通python的必要条件,也是熟练使用各数据类型.最大限度发挥它们功能的基本条件. pytho ...
- js中的节点遍历+类数组对象
firstChild 第一个子元素 lastChild 最后一个子元素 childNodes[n] = childNodes.item(n) 第n+1个子元素 parentNode ...
- PP: Time series anomaly detection with variational autoencoders
Problem: unsupervised anomaly detection Model: VAE-reEncoder VAE with two encoders and one decoder. ...
- 手写MyBatis流程
MyBatis 手写MyBatis流程 架构流程图 封装数据 封装到Configuration中 1.封装全局配置文件,包含数据库连接信息和mappers信息 2.封装*mapper.xml映射文件 ...
- 剑指offer-面试题29-顺时针打印矩阵-矩阵
/* 题目: 输入一个矩阵,按照从外到内顺时针的顺序依次打印每一个数字. */ /* 思路: 1.将打印矩阵看作是打印一个个从外向内的环. 2.每一个环都有一个起始节点,起始节点的坐标*2小于行数和列 ...
- json转dataset的另外一种解析方式自动生成guid强关联
/// <summary> /// 将json字符串自动转成dataset,并且自动补全主子关联关系, /// Guid,FKGuid /// Author:lijia /// date: ...
- (C语言)学生成绩排序-期末考倒数第二题结构体数组排序
假设学生的基本信息包括学号.姓名.三门课程成绩以及个人平均成绩,定义一个能够表示学生信息的结构类型.输入n(n<50)个学生的成绩信息,按照学生的个人平均分从高到低输出他们的信息.如果平均分相同 ...
- python3练习100题——040
原题链接:http://www.runoob.com/python/python-exercise-example40.html 题目:将一个数组逆序输出. a=[1,2,3,4,5] print a ...