上一篇说的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的更多相关文章

  1. CyclicBarrier回环屏障深度解析

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

  2. 回环栅栏CyclicBarrier

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

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

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

  4. thread_CyclicBarrier回环栅栏

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

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

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

  6. SharePoint回环检查(Loopback Check)相关问题

    Loopback Check(回环检查)本来不是一个SharePoint问题,是Windows Server为了增强自身安全性在Server 2003 SP1后引入的一个功能, 在近几个月中导致了一系 ...

  7. 关于STM32 CAN回环可用,正常不可用情况分析

    1.回环下应该与GPIO无关 2.GPIO是否初始化正确,时钟启用 3.是否复用,AFIO时钟是否启用 4.回环下是否有CAN_Tx应该有输出 5.终端电阻是否有 6.CAN收发器电路电压是否正常 7 ...

  8. linux回环网卡驱动设计

    回环网卡驱动 1.回环网卡和普通网卡的区别是他是虚拟的不是实际的物理网卡,它相当于把普通网卡的发送端和接收端短接在一起. 2.在内核源代码里的回环网卡程序(drivers/net/loopback.c ...

  9. VMware配置回环地址用于测试

           我们在开发过程中,很可能需要一台服务器用于测试,在这种环境下,我们很可能需要用到vmware来构建这样的开发环境.但如果当前处在一个离线,或是不在网内的环境下,我们所搭建的环境有可能无法 ...

随机推荐

  1. 如何实现广告响应式滚动 OwlCarousel2

    githu    https://github.com/OwlCarousel2/OwlCarousel2 OwlCarousel2 官方网址    http://owlcarousel2.githu ...

  2. C++中字符常量与字符常量不能直接相加

    定义string变量,并进行初始化,如下: string s1 = "Hello"; string s2 = s1 + "World"; string s3 = ...

  3. opencv —— HoughCircles 霍夫圆变换原理及圆检测

    霍夫圆变换原理 霍夫圆变换的基本原理与霍夫线变换(https://www.cnblogs.com/bjxqmy/p/12331656.html)大体类似. 对直线来说,一条直线能由极径极角(r,θ)表 ...

  4. 开发者的拯救者还是掘墓人?解密低代码开发平台 ZT

    据英国<金融时报>消息称,私募股权投资机构 KKR 和高盛共同筹集了 3.6 亿美元,以收购低代码开发平台 OutSystems 的“大量”少数股权,本次交易对 OutSystems 的估 ...

  5. 硬盘500M,为什么没有500M。10M宽带,为什么网速没有10M?

    在天朝, 硬件厂商用1000代替1024, 通信公司,用 byte来代替bit. 比如 500G的硬盘,应该有 500 * 1024 *1024 *8 = 4.194304*10^9 位 但是按照厂商 ...

  6. 安装Gitlab到CentOS(YUM)

    运行环境 系统版本:CentOS Linux release 7.3.1611 (Core) 软件版本:Gitlab-ce-11.10.1 硬件要求:最低2核4GB,建议4核8GB 安装过程 1.安装 ...

  7. 【终端使用】用户权限和"chmod"命令的简单使用

    一.用户权限知识点 1.1.基本概念 用户是Linux系统工作中重要的一环,用户管理包括 用户管理 和 组管理. 在Linux系统中,不论由本机登录系统 或者 远程登录系统,每个系统都必须拥有一个账号 ...

  8. 怎么在IDEA中给方法添加分割线?

    方法中间分割不清晰 怎么在IDEA中给方法添加分割线呢? 效果如图 方法上有一条分割线,比较明了 按照下列顺序点击修改设置即可 File→Settings→Editor→General→Appeara ...

  9. H5_0025:css3自适应布局单位vw,vh

    视口单位(Viewport units) 什么是视口? 在桌面端,视口指的是在桌面端,指的是浏览器的可视区域:而在移动端,它涉及3个视口:Layout Viewport(布局视口),Visual Vi ...

  10. 优先级队列-堆-STL实现

    #include <cstdio> #include <iostream> #include <queue> using namespace std; // 默认是 ...