CyclicBarrier循环屏障源码解析(基于jdk11)

1.1 CyclicBarrier概述

public class CyclicBarrier extends Object

CyclicBarrier被称为循环屏障/同步屏障,它可以使一定数量的线程反复在"屏障"上汇集,当线程达到"屏障"位置时将调用await(),这个方法将阻塞该线程直到所有线程都到达屏障位置。如果足够数量的线程都到达屏障位置,那么屏障将打开,此时所有的线程都将被唤醒进而释放执行,而屏障将被重置以便下次使用。

通过它可以实现让一组线程互相等待共同到达某个状态之后再全部同时执行,叫做“循环”是因为当足够数量的等待线程都被释放以后,CyclicBarrier可以被重复使用。

1.2 CyclicBarrier原理

1.2.1 基本结构(jdk11)

通过UML类图可知,CyclicBarrier是使用了ReentrantLock和Condition来完成屏障效果,本质上底层还是基于AQS的,只不过更加高级。

关键属性:

  1. parties:parties是在创建CyclicBarrier的时候指定的值,后续不可更改,表示屏障点数,或者说表示需要多少线程到达屏障(调用await)后,所有线程才会打破屏障继续往下运行
  2. count:count则初始化为parties的值,每当有一个线程到达屏障调用await方法之后,count就就递减1;当count 为0 时,表示所需要的所有线程都到了屏障,此时屏障可以被打破

变量parties始终用来记录所需总线程个数,而当count 值变为0后,又会将parties 的值赋给count,从而进行复用。使用两个变量的原因就是为了实现CyclicBarrier 的可复用性。

barrierCommand是一个任务,当所需要的线程都到达屏障后执行的回调任务。一个Generation内部类表示屏障的实现,generation属性表示当前屏障。

这里的lock锁用于控制线程并发的,保证代线程安全,await、reset、isBroken、getNumberWaiting方法都需要获取锁。

1.2.2 await()方法

public int await(long timeout, TimeUnit unit)
throws InterruptedException,
BrokenBarrierException,
TimeoutException {
return dowait(true, unit.toNanos(timeout));
}

这个await不是Condition的方法。调用CyclicBarrier 的await方法表示当前线程到达屏障点。如果当前线程不是将到达的最后一个线程,当前线程将一直等待。

满足下面条件之一将会被唤醒,可能还会抛出异常:

  1. 所需的所有线程都调用了await()方法(正常结束);
  2. 其他某个线程中断当前线程,则当前线程被唤醒并且清除当前线程的已中断状态,随后将打破当前屏障,并唤醒其他线程,最后抛出InterruptedException;
  3. 其他某个线程中断其他等待的线程,则当前线程被唤醒并抛出InterruptedException;
  4. 其他线程调用reset()方法打破屏障并重置屏障,则当前线程被唤醒并抛出BrokenBarrierException;
  5. 其他线程在等待当前屏障时超时,则当前线程被唤醒并抛出 BrokenBarrierException;
  6. 最后一个线程在执行回调任务过程中发生异常,则当前线程被唤醒并抛出 BrokenBarrierException。

如果当前线程是最后一个将要到达的线程,则当前线程不会等待,并且如果构造方法中提供了一个非空的回调任务,那么在允许其他线程继续运行之前(唤醒其他等待的线程之前),当前线程将运行该任务。如果在执行回调任务过程中发生异常,则该异常将传播到当前线程中,将 barrier 置于损坏状态,最后当前线程抛出该异常。

内部调用了dowait核心方法。

1.2.3 dowait方法

dowait是CyclicBarrier的核心方法,完成各种判断逻辑,比如等待、唤醒机制。

大概步骤如下:

  1. 首先就是获取lock锁,保证线程安全。

    final ReentrantLock lock = this.lock;
    lock.lock();
  2. 在一个try块中。获取当前屏障,使用局部变量g保存;

  3. 如果当前屏障被打破了,那么直接抛出BrokenBarrierException异常

  4. 如果当前线程被中断了,那么调用breakBarrier打破当前屏障,随后抛出InterruptedException异常。

     try {
    final Generation g = generation; if (g.broken)
    throw new BrokenBarrierException(); if (Thread.interrupted()) {
    breakBarrier();
    throw new InterruptedException();
    }
    ...
  5. count自减1,index记录到达的当前线程的索引,即自减1之后的count值;

  6. 如果index为0,那么表示当前线程是最后一个达到屏障的线程,所需要的所有线程都到达了屏障点:

    1. ranAction变量表示回调任务执行是否成功,初始化为false,表示执行失败;
    2. 开启一个try块:
      1. command变量记录回调任务;
      2. 如果command不为null,那么在当前线程(最后一个达到屏障的线程)中执行回调任务;
      3. 到这一步,表示command的执行没有抛出异常,那么ranAction设置为true;
      4. 调用nextGeneration重置屏障,并唤醒其他线程;
      5. 返回0,方法正常结束。
    3. 无论上面的有没有抛出异常(特指command任务的执行),都会执行finally代码块:
      1. 如果ranAction为false,表示command执行抛出了异常。那么调用breakBarrier打破当前屏障,并唤醒其他线程,随后抛出遇到的异常。

  1. 到这一步,表示index不为0,那么表示当前线程不是最后一个达到屏障的线程,可能需要等待。开启一个死循环:

    1. 开启一个开启一个try块:

      1. 如果是非超时等待,那么调用trip.await(),当前线程在trip条件变量上等待,直到被中断或者被唤醒。
      2. 否则,就是超时等待。如果超时时间大于0。那么调用trip.awaitNanos(),当前线程在trip条件变量上超时等待最多nanos纳秒,直到被中断或者被唤醒或者超时等待完毕,返回nanos,表示剩余超时等待时间。
    2. 在catch块中尝试捕获InterruptedException,即线程中断异常,如果捕获成功:
      1. 如果屏障g还是当前屏障,并且g没有被打破

        1. 那么当前线程调用breakBarrier打破当前屏障,并唤醒其他线程,随后抛出该异常。
      2. 否则,表示一种极端情况,即当前线程因为被中断而唤醒,但是由于cpu轮换,或者锁已被其他线程获取,还没有来得及打破屏障。此时最后一个线程就调用nextGeneration重置屏障成功,或者屏障被其他线程打破,或者屏障被其他线程reset。
        1. 设置当前线程的中断状态。这种情况如果是屏障正常打破,那么不需要抛出异常,算作等待完成,而如果是其他情况将会在下面判断并抛出异常!

  1. 到这一步,表示被唤醒或者超时时间到了,或者被中断但是其他线程更改了屏障设置的情况。如果屏障g被打破,那么抛出BrokenBarrierException异常。
  2. 如果屏障g不是当前屏障,说明最后一个线程已经到了,并且该屏障被打破并重置,返回index,正常结束。
  3. 如果是超时操作,并且等于超时时间小于等于0,那么说明是超时时间到了,并且该屏障还没有被打破。那么当前线程调用breakBarrier打破当前屏障,并唤醒其他线程,最后抛出TimeoutException异常。
  4. 到这里,说明屏障g既没有被打破也没有被替换,那么继续下一次循环,此时可能会继续等待。这种情况发生的概率很低,这种唤醒被称作“虚假唤醒”
  5. 最终需要在finally中释放lock锁。

1.2.3.1 breakBarrier打破屏障

在出现异常的时候调用的方法,且必须在获得锁之后才会调用。用于打破当前屏障,表明这个屏障已经失效了。主要做三件事:

  1. 打破当前屏障(broken设置为true);
  2. count重置为parties;
  3. 唤醒所有在trip条件变量上等待的线程。

1.2.3.2 nextGeneration重置屏障

在正常完成的时候调用的方法,且必须在获得锁之后才会调用。用于重置当前屏障,表明这个屏障已经使用完毕。主要做三件事:

  1. 唤醒所有在trip条件变量上等待的线程;
  2. count重置为parties;
  3. 重新初始化一个Generation对象,赋给generation,这就是下一个屏障。

可以看到,以前的屏障被第丢弃,但是并没有被打破(broken没有设置为true)。

1.2.4 await(timeout, unit)超时等待

public int await(long timeout,TimeUnit unit)

调用await方法表示当前线程到达屏障点。如果当前线程不是将到达的最后一个线程,当前线程将最多等待指定的超时时间。满足下面条件之一将会被唤醒,可能还会抛出异常:

与await()方法相似

1.2.5 reset重置屏障

将打破当前屏障并且重置新屏障。所有在屏障处等待的线程将会被唤醒并且抛出BrokenBarrierException。

实际上就是在获得锁之后连续调用breakBarrier和nextGeneration方法!

1.3 CyclicBarrier的总结

CyclicBarrier和之前学习CountDownLatch有些相似,但是又有区别:

  1. CountDownLatch和CyclicBarrier都能够实现线程之间的等待,只不过它们侧重点不同。CountDownLatch一般用于某个或者某一批线程等待若干个其他线程执行完任务之后,它才执行;而CyclicBarrier一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行;
  2. 同一个线程中调用多次CountDownLatch的countDown方法,计数器就会减去多次;而同一线程中调用多次cyclicBarrier的await方法,还是只会算作一条线程到达当前屏障,因为调用一次await之后就会等待,而在所有线程都到达屏障之后,屏障开放并且重置,后续的await方法将算作在新屏障上的等待!
  3. CountDownLatch的计数器只能使用一次,而CyclicBarrier的屏障可以使用reset()方法重置,也会自动重置,所以CyclicBarrier能处理更为复杂的业务场景。
  4. CountDownLatch是使用原始的AQS框架实现的,而CyclicBarrier使用的则是更加高级的组件ReentrantLock和Condition,但是追根溯源,这两个组件也是依赖AQS实现的。

CyclicBarrier循环屏障源码解析(基于jdk11)的更多相关文章

  1. String,StringBuffer和StringBuilder源码解析[基于JDK6]

    最近指导几位新人,学习了一下String,StringBuffer和StringBuilder类,从反馈的结果来看,总体感觉学习的深度不够,没有读出东西.其实,JDK的源码是越读越有味的.下面总结一下 ...

  2. 【Java并发集合】ConcurrentHashMap源码解析基于JDK1.8

    concurrentHashMap(基于jdk1.8) 类注释 所有的操作都是线程安全的,我们在使用时无需进行加锁. 多个线程同时进行put.remove等操作时并不会阻塞,可以同时进行,而HashT ...

  3. Spring源码解析-基于注解依赖注入

    在spring2.5版本提供了注解的依赖注入功能,可以减少对xml配置. 主要使用的是 AnnotationConfigApplicationContext: 一个注解配置上下文 AutowiredA ...

  4. Java 集合系列05之 LinkedList详细介绍(源码解析)和使用示例

    概要  前面,我们已经学习了ArrayList,并了解了fail-fast机制.这一章我们接着学习List的实现类——LinkedList.和学习ArrayList一样,接下来呢,我们先对Linked ...

  5. Java 集合系列03之 ArrayList详细介绍(源码解析)和使用示例

    概要 上一章,我们学习了Collection的架构.这一章开始,我们对Collection的具体实现类进行讲解:首先,讲解List,而List中ArrayList又最为常用.因此,本章我们讲解Arra ...

  6. Java 集合系列06之 Vector详细介绍(源码解析)和使用示例

    概要 学完ArrayList和LinkedList之后,我们接着学习Vector.学习方式还是和之前一样,先对Vector有个整体认识,然后再学习它的源码:最后再通过实例来学会使用它.第1部分 Vec ...

  7. Java 集合系列10之 HashMap详细介绍(源码解析)和使用示例

    概要 这一章,我们对HashMap进行学习.我们先对HashMap有个整体认识,然后再学习它的源码,最后再通过实例来学会使用HashMap.内容包括:第1部分 HashMap介绍第2部分 HashMa ...

  8. Java 集合系列 09 HashMap详细介绍(源码解析)和使用示例

    java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...

  9. Java 集合系列 05 Vector详细介绍(源码解析)和使用示例

    java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...

  10. Java 集合系列 04 LinkedList详细介绍(源码解析)和使用示例

    java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...

随机推荐

  1. 使用Metricbeat监控zookeeper遇到的问题

    1.metricbeat中启动自动加载模块 metricbeat.config.modules: path: ${path.config}/modules.d/*.yml reload.enabled ...

  2. Elasticsearch索引和查询性能调优的21条建议

    Elasticsearch部署建议 1. 选择合理的硬件配置:尽可能使用 SSD Elasticsearch 最大的瓶颈往往是磁盘读写性能,尤其是随机读取性能.使用SSD(PCI-E接口SSD卡/SA ...

  3. windows系统下使用bat脚本文件设置MySQL系统环境变量

    说明:在一个bat文件中设置tomcat环境变量后,不能直接使用,需要另起一个bat文件才能使用 号开头的行不要写在bat文件中 # 这个bat文件实现的功能:设置环境变量 @echo off set ...

  4. ATT&CK系列一 知识点总结

    一.环境搭建1.环境搭建测试2.信息收集二.漏洞利用3.漏洞搜索与利用4.后台Getshell上传技巧5.系统信息收集6.主机密码收集三.内网搜集7.内网--继续信息收集8.内网攻击姿势--信息泄露9 ...

  5. 一文读懂Apache Geode缓存中间件

    目录 一.对缓存中间件的诉求 1.1 我们为什么需要缓存中间件 1.2 缓存的分类 1.1.1 弱势缓存 1.1.2 强势缓存 二.什么是Apache Geode 2.1 Apache Geode的架 ...

  6. .NET平台下一个你不知道的框架,我只想说两个字:“牛逼”

    框架内容 零度框架是一套基于微服务和领域模型驱动设计的企业级快速开发框架,基于微软 .NET 6 + React 最新技术栈构建,容器化微服务最佳实践,零度框架的搭建以开发简单,多屏体验,前后端分离, ...

  7. 在电脑主机(MainFrame)中只需要按下主机的开机按钮(on()),即可调用其它硬件设备和软件的启动方法,如内存(Memory)的自检(check())、CPU的运行(run())、硬盘(Hard

    欢迎大家加入我的社区:http://t.csdn.cn/Q52km 社区中不定时发红包 文章目录 1.UML类图 2.源码 3.优缺点 1.UML类图 2.源码 package com.zheng; ...

  8. 记录一次使用git工具拉取coding上代码密码账号错误的经历

    1.忘记密码 1.另外的一个位置

  9. 那齐博x3又什么什么?

    那齐博x3又什么什么? 齐博x3是齐博X1/齐博x2之后的升级版本. 主要优化圈子系统

  10. AI人脸识别+换脸

    视频换脸可参考 https://github.com/iperov/DeepFaceLab import dlib.dlib as dlib import numpy import sys impor ...