并发编程之循环屏障CyclicBarrier

news2024/11/20 8:22:49

文章目录

    • 前言
    • 什么是CyclicBarrier
    • CyclicBarrier原理
    • CyclicBarrier VS CountDownLatch
      • CountDownLatch图示:
      • CyclicBarrier图示:
      • 两者的异同:
    • CyclicBarrier核心源码
    • 实战演示
      • 1、创建测试demo
      • 2、创建测试用例
      • 3、查看测试结果
    • 写在最后

前言

前面我们分享了线程等待其他线程执行同步类CountDownLatch,CountDownLatch是一个或多个线程等待其他线程执行完成才执行,比如主线程等待其他多个子线程执行完成再执行主线程逻辑。但是,在实际生产开发过程中有些场景需要多个线程相互等待,比如合并计算结果、比赛计时等等,这个时候我们就需要用到循环屏障Cyclicbarrier。

什么是CyclicBarrier

CyclicBarrier翻译为循环屏障,正如其名它是一个可以循环使用,可以让一组线程相互等待,线程只有全部到达屏障才能使屏障消失的一个同步辅助类。

CyclicBarrier原理

CyclicBarrier内部使用可重入ReentrantLock保证线程同步(并发编程之可重入锁ReentrantLock),使用Condition来保证线程阻塞隔离和唤醒,使用组parties来保存可重用副本个数,使用–count来计算还没有到达屏障的线程数目。在实际的运行过程中,CyclicBarrier会使用count值模拟一个屏障,count > 0则表示还有没有到达屏障的线程,此时当前线程需要加入阻塞;count == 0则表示所有线程都已经到达屏障点,此时会重置CyclicBarrier为下次重用做准备并唤醒所有阻塞线程。

CyclicBarrier VS CountDownLatch

CountDownLatch图示:

在这里插入图片描述

CyclicBarrier图示:

在这里插入图片描述

两者的异同:

1、CyclicBarrier 与 CountDownLatch 都是同步辅助类,都达到了线程同步的效果;
2、CyclicBarrier 是多个线程相互等待,CountDownLatch则是一个或多个等待其他线程;
3、CyclicBarrier 内部使用可重入锁ReentrantLock保证同步,CountDownLatch 基于抽象同步队列AQS,由于ReentrantLock 也是基于AQS实现,故两者的同步逻辑都差距不大。

CyclicBarrier核心源码

我们进入JUC CyclicBarrier 核心源码:

//重置cyclicbarrier
private void nextGeneration() {
    // signal completion of last generation
    trip.signalAll();
    // set up next generation
    count = parties;
    generation = new Generation();
}

/**
 * 将当前屏障设置为已经打破并唤醒所有阻塞
 */
private void breakBarrier() {
    generation.broken = true;
    count = parties;
    trip.signalAll();
}

/**
 * 核心方法
 */
private int dowait(boolean timed, long nanos)
    throws InterruptedException, BrokenBarrierException,
           TimeoutException {
    //可重入锁保证同步           
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        final Generation g = generation;

        //是否已经打破屏障验证
        if (g.broken)
            throw new BrokenBarrierException();
        //线程打断验证
        if (Thread.interrupted()) {
            //打破屏障
            breakBarrier();
            throw new InterruptedException();
        }

        int index = --count;
        if (index == 0) {  // tripped
            boolean ranAction = false;
            try {
                final Runnable command = barrierCommand;
                if (command != null)
                    command.run();
                ranAction = true;
                //重置屏障并唤醒所有阻塞线程
                nextGeneration();
                return 0;
            } finally {
                if (!ranAction)
                    breakBarrier();
            }
        }

        // 线程自旋加入阻塞并增加超时逻辑
        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();
    }
}

如上源码所示,我们对主要的步骤做了相对的注释。
CyclicBarrier的核心部分就是dowait()方法,我们外部调用的await()方法实际就是调用的这个方法。
dowait()方法内部使用可重入锁ReentrantLock保证线程同步,该方法的主要逻辑是:
1、首先会验证当前屏障是否已经打破,当前线程是否已经被打断,如果已经被打破或者打断则直接会释放所有阻塞线程;
2、然后会将计数器–count,如果count == 0 则表示所有线程到达屏障点,此时会先执行构造方法传入的线程,后调用nextGeneration()方法重置CyclicBarrier并唤醒所有阻塞线程;如果count > 0 则表示还有线程没有到达屏障,则当前线程自旋加入阻塞。

实战演示

之前参加过的一道面试机试题目:请模拟出赛马比赛中各个赛马到达终点的时间。

这个题目的场景其实就是要让每个赛马都准备好了发令开始计时,然后各个赛马根据自身的情况完成比赛给出结束时间。这里我们可以用CyclicBarrier 模拟赛马开跑的时候,然后我们随机让线程睡眠模拟赛马跑步时间,最后打印出各个赛马到达的时间即可。

1、创建测试demo

/**
 * CyclicBarrier模拟赛马比赛
 * @author senfel
 * @version 1.0
 * @date 2023/5/8 11:27
 */
public class CyclicBarrierDemo {


    /**
     * 比赛方法
     * @author senfel
     * @date 2023/5/8 11:29
     * @return void
     */
    public static void matchFun() throws Exception{
        //线程池模拟赛马池
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        //cyclicBarrier 屏障模拟发令
        CyclicBarrier cyclicBarrier = new CyclicBarrier(5);
        //时间格式化
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("HH:mm:ss");
        //模拟比赛结束
        CountDownLatch countDownLatch = new CountDownLatch(5);
        //模拟比赛,每场比赛5匹赛马
        for(int i=0;i<5;i++){
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    //赛马等待发令
                    System.err.println("赛马:"+Thread.currentThread().getName()+"等待发令");
                    try {
                        cyclicBarrier.await();
                        LocalDateTime startTime = LocalDateTime.now();
                        System.err.println("赛马:"+Thread.currentThread().getName()+"开始起跑,起跑时间为:"+ startTime.format(dateTimeFormatter));
                        //线程随机睡眠模拟赛马赛跑时间
                        int time = new Random().nextInt(20)  * 1000;
                        Thread.sleep(time);
                        LocalDateTime endTime = LocalDateTime.now();
                        Duration between = Duration.between(startTime, endTime);
                        long millis = between.toMillis();
                        System.err.println("赛马:"+Thread.currentThread().getName()+"完成比赛," +
                                "起跑时间为:"+startTime.format(dateTimeFormatter)+"," +
                                "到达时间为:"+endTime.format(dateTimeFormatter)+"," +
                                "耗时:"+millis+"毫秒。");
                        countDownLatch.countDown();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            });
        }
        countDownLatch.await();
        System.err.println("比赛完成");
        executorService.shutdown();
    }
}

2、创建测试用例

@SpringBootTest
class DemoApplicationTests {

/**
 * 模拟赛马测试用例
 * @author senfel
 * @date 2023/5/8 13:42
 * @return void
 */
public void matchTest() throws Exception{
    CyclicBarrierDemo.matchFun();
}

}

3、查看测试结果

赛马:pool-1-thread-2等待发令
赛马:pool-1-thread-1等待发令
赛马:pool-1-thread-3等待发令
赛马:pool-1-thread-5等待发令
赛马:pool-1-thread-4等待发令

赛马:pool-1-thread-3开始起跑,起跑时间为:12:33:51
赛马:pool-1-thread-1开始起跑,起跑时间为:12:33:51
赛马:pool-1-thread-2开始起跑,起跑时间为:12:33:51
赛马:pool-1-thread-4开始起跑,起跑时间为:12:33:51
赛马:pool-1-thread-5开始起跑,起跑时间为:12:33:51

赛马:pool-1-thread-2完成比赛,起跑时间为:12:33:51,到达时间为:12:33:55,耗时:4002毫秒。
赛马:pool-1-thread-1完成比赛,起跑时间为:12:33:51,到达时间为:12:33:57,耗时:6002毫秒。
赛马:pool-1-thread-3完成比赛,起跑时间为:12:33:51,到达时间为:12:33:58,耗时:7002毫秒。
赛马:pool-1-thread-4完成比赛,起跑时间为:12:33:51,到达时间为:12:34:03,耗时:12002毫秒。
赛马:pool-1-thread-5完成比赛,起跑时间为:12:33:51,到达时间为:12:34:10,耗时:19002毫秒。

比赛完成

写在最后

CyclicBarrier循环屏障是多线程并发编程中的常用同步类,我们可以用它来实现多个线程相互等待,并且可重用。其中的dowait()方式是核心方法,用可重入锁ReentrantLock保证了方法逻辑同步功能,使用Condition的await()、signalAll()方法来阻塞和唤醒线程。

⭐️路漫漫其修远兮,吾将上下而求索 🔍

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

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

相关文章

Ubuntu 20.04安装mysql8并配置远程访问

文章目录 一、使用apt-get安装mysql服务二、初始化mysql数据库管理员用户密码三、配置远程访问 一、使用apt-get安装mysql服务 # 更新软件源 apt-get install update# 安装mysql服务 apt-get install mysql-server# 使用mysqladmin工具查看mysql版本 mysqladmin --version# 启…

powershell定义文本,用户交互,正则表达式

定义文本 PS C:\Users\Administrator> $site"yuan" PS C:\Users\Administrator> $text"$site $(get-date) $env:windir" PS C:\Users\Administrator> $text yuan 09/16/2022 14:12:26 C:\Windows#使用单引号闭合字符串输出双引号 The site of my…

【Jeston Orin】Orin nano 8G模块使用官方系统包生成标准烧写系统测试

大家好&#xff0c;我是虎哥&#xff0c;GTC 2023上&#xff0c;NVIDIA正式推出了面向边缘AI的新一代入门款开发套件&#xff0c;Jetson Orin Nano Developer Kit。虽说只是入门套件&#xff0c;但据说相比上一代Jetson Nano有最高达80倍的性能提升&#xff01;于是我在收到包裹…

苹果ipad触控笔哪个好?平价电容笔排行榜

因为ipad本身的性能足够强大&#xff0c;所以现在已经有不少人开始使用它了。大屏幕上的教学效果很好&#xff0c;但如果只是为了用来看电视剧&#xff0c;那就没什么用了。如果你不想买一支价格昂贵的苹果电容笔&#xff0c;或只想用来做个学习笔记&#xff0c;这时&#xff0…

SpringBoot整合Nacos配置中心和注册中心

一、背景 公司项目中使用的Nacos作为服务的注册中心和配置中心&#xff0c;但是呢公司的这一套Nacos是经过封装了的&#xff0c;而且封装的不是很友好&#xff0c;想着自己搭建一套标注的Nacos配置中心和服务中心 二、Nacos配置中心和注册中心搭建 2.1 依赖引入 <!--注册…

端点中心配置

什么是桌面管理 桌面管理是管理组织内所有计算机系统的综合方法。尽管名称如此&#xff0c;桌面管理还包括监督组织内使用的笔记本电脑和其他计算设备。对于IT经理来说&#xff0c;使用户的计算机保持最新状态可能是一个挑战&#xff0c;特别是考虑到升级软件以防止安全漏洞的…

【Ubuntu18.04】Docker配置镜像源

作者主页&#xff1a;爱笑的男孩。的博客_CSDN博客-深度学习,活动,YOLO领域博主爱笑的男孩。擅长深度学习,活动,YOLO,等方面的知识,爱笑的男孩。关注算法,python,计算机视觉,图像处理,深度学习,pytorch,神经网络,opencv领域.https://blog.csdn.net/Code_and516?typeblog个人简…

【二维矩阵如何存储在一维数组中(行优先和列优先)】

列优先和行优先的性能取决于具体的硬件架构和代码访问模式。在现代计算机中,内存访问的局部性(locality of reference)对性能至关重要。局部性分为两类:时间局部性(temporal locality)和空间局部性(spatial locality)。时间局部性表示最近访问过的数据项很可能在不久的…

加拿大留学思路自理

首先先看加拿大地图 留学加拿大的思路就应该是这样的&#xff1a; 1、清楚自己的需求 比如自己是移民向&#xff0c;所以首先就应该去加拿大官方网站Immigration and citizenship - Canada.ca 因为自己是理工科&#xff0c;之前在网络上看到别人总结的信息是说BC省理工类硕士…

【C++学习】类模板

类模板语法 #include<iostream> #include<string> using namespace std; //模板并不是万能的&#xff0c;有些特定数据类型&#xff0c;需要具体化方式做特殊实现 template<class NameType,class AgeType> class person { public:person(NameType name, Age…

k-means、决策树、svm算法总结

一、k-means算法 聚类算法&#xff1a; 一种典型的 无监督 学习算法&#xff0c;主要用于将相似的样本自动归到一个类别中。 在聚类算法中根据样本之间的相似性&#xff0c;将样本划分到不同的类别中&#xff0c;对于不同的相似度计算方法&#xff0c;会得到不同的聚类结果&…

【亲测有效】GnuTLS recv error (-110): The TLS connection was non-properly terminated.

【亲测有效】GnuTLS recv error [-110]: The TLS connection was non-properly terminated. 问题描述解决方法一&#xff1a;【取消代理】方法二【如果取消代理无用】方法三【这种方法对我有效】 问题描述 fatal: unable to access ‘https://github.com/openai/CLIP.git/’: …

JMeter压力测试案例(商品超卖并发问题)

什么要对接口压测呢? 压力测试可以用来验证软件系统的稳定性和可靠性&#xff0c;在压力下测试系统的性能和稳定性&#xff0c;发现并解决潜在的问题&#xff0c;确保系统在高负载情况下不会崩溃。压力测试可以用来评估软件系统的容量和性能&#xff0c;通过模拟高负载情况下…

2023年5月学习,6月考试DAMA-CDGA/CDGP数据治理认证

6月18日DAMA-CDGA/CDGP数据治理认证考试开放报名中&#xff01; 考试开放地区&#xff1a;北京、上海、广州、深圳、长沙、呼和浩特、杭州、南京、济南、成都、西安。其他地区凑人数中… DAMA-CDGA/CDGP数据治理认证班进行中&#xff0c;快来报名加入学习吧&#xff01; DAMA认…

谈谈接口 0.0

目录 接口的概念 接口语法 接口的成员变量与方法 接口的使用 实现多个接口 接口的概念 在现实生活中&#xff0c;接口的例子比比皆是&#xff0c;比如&#xff1a;笔记本上的USB口&#xff0c;电源插座等... 电脑的USB口上&#xff0c;可以插&#xff1a;U盘、鼠标、键盘…

three.js 基础入门

总体思路&#xff1a; 1. 创建场景 2. 创建物体&#xff08;指定几何体、材质&#xff09; 3. 把物体加入场景 4. 创建相机&#xff08;指定机位及拍摄对象&#xff09; 5. 创建渲染器&#xff08;指定画布大小&#xff0c;渲染场景和相机&#xff09; // 1. 创建场景const s…

【图像分割】【深度学习】Windows10下f-BRS官方代码Pytorch实现

【图像分割】【深度学习】Windows10下f-BRS官方代码Pytorch实现 提示:最近开始在【图像分割】方面进行研究,记录相关知识点,分享学习中遇到的问题已经解决的方法。 文章目录 【图像分割】【深度学习】Windows10下f-BRS官方代码Pytorch实现前言f-BRS模型运行环境安装1.下载源码并…

MySQL基础(十三)约束

1. 约束(constraint)概述 1.1 为什么需要约束 数据完整性&#xff08;Data Integrity&#xff09;是指数据的精确性&#xff08;Accuracy&#xff09;和可靠性&#xff08;Reliability&#xff09;。它是防止数据库中存在不符合语义规定的数据和防止因错误信息的输入输出造成…

Ubuntu20.04 不能访问磁盘? 挂载/添加磁盘

Ubuntu20.04 不能访问磁盘&#xff1f; 挂载/添加磁盘 1. 判断是否挂载磁盘2. 格式化磁盘3. 挂载磁盘4. 设置磁盘权限5. 重启系统 一般设备刚拿回来的是不会全部挂载的&#xff0c;也就是说&#xff0c;一部分硬盘&#xff08;机械硬盘&#xff09;是需要我们自己挂载的&…

如何通过舆情监测系统加强数据分析和舆情管控?

舆情监测系统是一种可以帮助企业、政府等机构了解并掌握公众对其关注的程度&#xff0c;以及对其言论和行动的态度和反应的工具。随着社交媒体的兴起&#xff0c;舆情监测系统也越来越重要&#xff0c;越来越受到各个领域的关注和应用。本文将通过分析国内的具体案例&#xff0…