CyclicBarrier:人齐了,老司机就发车了!

作者 | 王磊

来源 | Java中文社群(ID:javacn666)

转载请联系授权(微信ID:GG_Stone)

上一篇咱讲了 CountDownLatch 可以解决多个线程同步的问题,相比于 join 来说它的应用范围更广,不仅可以应用在线程上,还可以应用在线程池上。然而 CountDownLatch 却是一次性的计数器,以王者农药来说,咱们不可能一场团战就决定比赛的输赢,所以在某些场景下,咱们是需要重复使用某个等待功能的,这就是我们今天要介绍的另一个主角——CyclicBarrier。

CyclicBarrier

CyclicBarrier 翻译为中文是循环(Cyclic)栅栏(Barrier)的意思,它的大概含义是实现一个可循环利用的屏障。

CyclicBarrier 作用是让一组线程相互等待,当达到一个共同点时,所有之前等待的线程再继续执行,且 CyclicBarrier 功能可重复使用。

举个栗子

比如磊哥要坐班车回老家,因为中途不允许上、下乘客,所以营运的公司为了收益最大化,就会等人满之后再发车。像这种等人坐满就发一班车的场景,就是 CyclicBarrier 所擅长的,因为它可以重复使用(不像 CountDownLatch 那样只能用一次)。

CyclicBarrier VS CountDownLatch

CountDownLatch:一个或者多个线程,等待另外 N 个线程完成某个事情之后才能执行。

CountDownLatch 就像玩王者农药开局的加载一样,所有人要等待其他人都加载 100% 之后才能开始游戏。

CyclicBrrier:N 个线程相互等待,直到有足够数量的线程都到达屏障点之后,之前等待的线程就可以继续执行了。

CyclicBrrier 就像老司机开车一样,如果车上还有空余的座位,那么所有人都得等着,直到座位被坐满之后,老司机才会发车。

CyclicBarrier使用

import java.util.Date;
import java.util.Random;
import java.util.concurrent.*;public class CyclicBarrierExample {public static void main(String[] args) {// 创建 CyclicBarrierfinal CyclicBarrier cyclicBarrier = new CyclicBarrier(2, new Runnable() {@Overridepublic void run() {System.out.println("人满了,准备发车:" + new Date());}});// 线程调用的任务Runnable runnable = new Runnable() {@Overridepublic void run() {// 生成随机数 1-3int randomNumber = new Random().nextInt(3) + 1;// 进入任务System.out.println(String.format("我是:%s 再走:%d 秒就到车站了,现在时间:%s",Thread.currentThread().getName(), randomNumber, new Date()));try {// 模拟执行TimeUnit.SECONDS.sleep(randomNumber);// 调用 CyclicBarriercyclicBarrier.await();// 任务执行System.out.println(String.format("线程:%s 上车,时间:%s",Thread.currentThread().getName(), new Date()));} catch (InterruptedException e) {e.printStackTrace();} catch (BrokenBarrierException e) {e.printStackTrace();}}};// 创建线程池ExecutorService threadPool = Executors.newFixedThreadPool(10);// 执行任务 1threadPool.submit(runnable);// 执行任务 2threadPool.submit(runnable);// 执行任务 3threadPool.submit(runnable);// 执行任务 4threadPool.submit(runnable);// 等待所有任务执行完终止线程池threadPool.shutdown();}
}

以上代码执行结果如下:

从上述结果可以看出:当 CyclicBarrier 的计数器设置为 2 时,线程 2 和 线程 3 都到屏障点之后,老司机才会发第一波车,再 2s 之后,线程 1 和线程 4 也同时进入了屏障点,这时候老司机又可以再发一波车了。

实现原理

我们先来看下 CyclicBarrier 的类图:

由上图可知 CyclicBarrier 是基于独占锁 ReentrantLock 实现的,其底层也是基于 AQS 的。

在 CyclicBarrier 类的内部有一个计数器 count,当 count 不为 0 时,每个线程在到达屏障点会先调用 await 方法将自己阻塞,此时计数器会减 1,直到计数器减为 0 的时候,所有因调用 await 方法而被阻塞的线程就会被唤醒继续执行。当 count 计数器变成 0 之后,就会进入下一轮阻塞,此时 parties(parties 是在 new CyclicBarrier(parties) 时设置的值)会将它的值赋值给 count 从而实现复用。

常用方法

CyclicBarrier(parties):初始化相互等待的线程数量的构造方法。

CyclicBarrier(parties,Runnable barrierAction):初始化相互等待的线程数量以及屏障线程的构造方法,当 CyclicBarrier 的计数器变为 0 时,会执行 barrierAction 构造方法。

getParties():获取 CyclicBarrier 打开屏障的线程数量,也称为方数。

getNumberWaiting():获取正在CyclicBarrier上等待的线程数量。

await():在 CyclicBarrier 上进行阻塞等待,直到发生以下情形之一:在 CyclicBarrier 上等待的线程数量达到 parties,则所有线程被释放,继续执行;

  • 当前线程被中断,则抛出 InterruptedException 异常,并停止等待,继续执行;

  • 其他等待的线程被中断,则当前线程抛出 BrokenBarrierException 异常,并停止等待,继续执行;

  • 其他等待的线程超时,则当前线程抛出 BrokenBarrierException 异常,并停止等待,继续执行;

  • 其他线程调用 CyclicBarrier.reset() 方法,则当前线程抛出 BrokenBarrierException 异常,并停止等待,继续执行。

await(timeout,TimeUnit):在CyclicBarrier上进行限时的阻塞等待,直到发生以下情形之一:

  • 在 CyclicBarrier 上等待的线程数量达到 parties,则所有线程被释放,继续执行;

  • 当前线程被中断,则抛出 InterruptedException 异常,并停止等待,继续执行;

  • 当前线程等待超时,则抛出 TimeoutException 异常,并停止等待,继续执行;

  • 其他等待的线程被中断,则当前线程抛出 BrokenBarrierException 异常,并停止等待,继续执行;

  • 其他等待的线程超时,则当前线程抛出 BrokenBarrierException 异常,并停止等待,继续执行;

  • 其他线程调用 CyclicBarrier.reset() 方法,则当前线程抛出 BrokenBarrierException 异常,并停止等待,继续执行。

isBroken():获取是否破损标志位 broken 的值,此值有以下几种情况:

  • CyclicBarrier 初始化时,broken=false,表示屏障未破损;

  • 如果正在等待的线程被中断,则 broken=true,表示屏障破损;

  • 如果正在等待的线程超时,则 broken=true,表示屏障破损;

  • 如果有线程调用 CyclicBarrier.reset() 方法,则 broken=false,表示屏障回到未破损状态。

reset():使得CyclicBarrier回归初始状态,直观来看它做了两件事:

  • 如果有正在等待的线程,则会抛出 BrokenBarrierException 异常,且这些线程停止等待,继续执行。

  • 将是否破损标志位 broken 置为 false。

总结

CyclicBrrier 是通过独占锁 ReentrantLock 实现计数器的原子性更新的,CyclicBrrier 最常用的是 await() 方法,使用此方法会将计数器 -1,并判断当前的计数器是否为 0,如果不为 0 就会阻塞等待,并计时器为 0 之后,才能继续执行剩余任务。CyclicBrrier 相比于 CountDownLatch 来说,它的优势在于可以重复使用。

参考 & 鸣谢

blog.csdn.net/qq_39241239/article/details/87030142

blog.csdn.net/zzg1229059735/article/details/61191679

www.cnblogs.com/yaochunhui/p/13494689.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/544872.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

iOS平台快速发布HT for Web拓扑图应用

iOS平台一直是封闭的生态圈,iOS开发者要缴纳年费加入开发者计划才可进行iOS平台的APP开发测试,所开发的APP需要上传到App Store经过苹果审核以后才可对外发布。如果要开发企业内部应用,则要缴纳更高的费用购买企业账户才可以。 对于现在火如荼…

事务注解 @Transactional 失效的3种场景及解决办法

Transactional失效场景第一种 Transactional注解标注方法修饰符为非public时,Transactional注解将会不起作用。例如以下代码,定义一个错误的Transactional标注实现,修饰一个默认访问符的方法:/*** author zhoujy**/ Component pub…

Android的多语言实现

文章转自:http://blog.csdn.net/barryhappy/article/details/23436527 以前就知道Android的多语言实现很简单,可以在不同的语言环境下使用不同的资源什么的,但是一直没有实际使用过。 最近公司的项目要用到多语言于,是就研究了一下…

厉害了,自己手写一个Java热加载!

热加载:在不停止程序运行的情况下,对类(对象)的动态替换。Java ClassLoader 简述Java中的类从被加载到内存中到卸载出内存为止,一共经历了七个阶段:加载、验证、准备、解析、初始化、使用、卸载。接下来我们…

公司新来的小可爱,竟然把内存搞崩了!

ThreadLocal使用不规范,师傅两行泪组内来了一个实习生,看这小伙子春光满面、精神抖擞、头发微少,我心头一喜:绝对是个潜力股。于是我找经理申请亲自来带他,为了帮助小伙子快速成长,我给他分了一个需求&…

理解Node.js的event loop

为什么80%的码农都做不了架构师?>>> 关于Node.js的第一个基本概念是I/O操作开销是巨大的: 所以,当前变成技术中最大的浪费来自于等待I/O操作的完成。有几种方法可以解决性能的影响: 同步方式:按次序一个…

硬核|定时任务的10种实现方案,满足你的不同需求!

最近有几个读者私信给我,问我他们的业务场景,要用什么样的定时任务。确实,在不用的业务场景下要用不同的定时任务,其实我们的选择还是挺多的。我今天给大家总结10种非常实用的定时任务,总有一种是适合你的。一. linux自…

Semaphore自白:限流器用我就对了!

作者 | 王磊来源 | Java中文社群(ID:javacn666)转载请联系授权(微信ID:GG_Stone)大家好,我是 Semaphore,我的中文名字叫“信号量”,我来自 JUC 家族(java.uti…

Android Activity和Intent机制学习笔记

文章转自:http://www.cnblogs.com/feisky/archive/2010/01/16/1649081.html Activity Android中,Activity是所有程序的根本,所有程序的流程都运行在Activity之中,Activity具有自己的生命周期(见http://www.cnblogs.com…

线程的故事:我的3位母亲成就了优秀的我!

作者 | 王磊来源 | Java中文社群(ID:javacn666)转载请联系授权(微信ID:GG_Stone)声明:本故事纯属虚构,如果雷同那就是真事了!大家好,我是线程,我的…

7种内存泄露场景和13种解决方案!

前言Java通过垃圾回收机制,可以自动的管理内存,这对开发人员来说是多么美好的事啊。但垃圾回收器并不是万能的,它能够处理大部分场景下的内存清理、内存泄露以及内存优化。但它也并不是万能的。不然,我们在实践的过程中也不会出现…

Android Sqlite

2019独角兽企业重金招聘Python工程师标准>>> 一、http://lansuiyun.iteye.com/blog/1246430 good! 二、增加新表时,需要更新版本号。 三、Android 使用自定义cursorAdapter: http://blog.csdn.net/buaalei/article/details/6064792 good! 四…

小黑小波比.git clone报错解决方案

2019独角兽企业重金招聘Python工程师标准>>> zmzpzmzp1:~/data$ git clone git192.168.199.199:zmw/s910.git 正克隆到 s910... ssh: connect to host 192.168.199.199 port 22: Connection refused fatal: Could not read from remote repository.Please make sure…

一个sql注入直接把我们服务搞挂了

前言最近我在整理安全漏洞相关问题,准备在公司做一次分享。恰好,这段时间团队发现了一个sql注入漏洞:在一个公共的分页功能中,排序字段作为入参,前端页面可以自定义。在分页sql的mybatis mapper.xml中,orde…

reinterpret_cast和static_cast的总结

主要参考:http://blog.csdn.net/querw/article/details/7387594 http://www.cnblogs.com/jerry19880126/archive/2012/08/14/2638192.html http://www.cnblogs.com/ider/archive/2011/07/30/cpp_cast_operator_part3.htmlhttp://bbs.csdn.net/topics/390249118 关键…

Java双刃剑之Unsafe类详解

前一段时间在研究juc源码的时候,发现在很多工具类中都调用了一个Unsafe类中的方法,出于好奇就想要研究一下这个类到底有什么作用,于是先查阅了一些资料,一查不要紧,很多资料中对Unsafe的态度都是这样的画风&#xff1a…

Java知多少(66)输入输出(IO)和流的概述

输入输出(I/O)是指程序与外部设备或其他计算机进行交互的操作。几乎所有的程序都具有输入与输出操作,如从键盘上读取数据,从本地或网络上的文件读取数据或写入数据等。通过输入和输出操作可以从外界接收信息,或者是把信…

额!Java中用户线程和守护线程区别这么大?

作者 | 王磊来源 | Java中文社群(ID:javacn666)转载请联系授权(微信ID:GG_Stone)在 Java 语言中线程分为两类:用户线程和守护线程,而二者之间的区别却鲜有人知,所以本文磊…

EasyUI-右键菜单变灰不可用效果

使用过EasyUI的朋友想必都知道疯狂秀才写的后台界面吧,作为一个初学者我不敢妄自评论它的好坏,不过它确实给我们提供了一个很好框架,只要在它的基础上进行修改,基本上都可以满足我们开发的需要。 知道“疯狂秀才”写的后台界面已经…

ThreadLocal中的3个大坑,内存泄露都是小儿科!

我在参加Code Review的时候不止一次听到有同学说:我写的这个上下文工具没问题,在线上跑了好久了。其实这种想法是有问题的,ThreadLocal写错难,但是用错就很容易,本文将会详细总结ThreadLocal容易用错的三个坑&#xff…