CyclicBarrier线程同步

news2024/11/17 1:55:46

关于作者: CSDN内容合伙人、技术专家, 从零开始做日活千万级APP,带领团队单日营收超千万。
专注于分享各领域原创系列文章 ,擅长java后端、移动开发、商业化变现、人工智能等,希望大家多多支持。

目录

  • 一、导读
  • 二、概览
      • CyclicBarrier 和 countdownlatch 的区别
  • 三、使用
  • 四、原理
  • 五、 推荐阅读

ddd

一、导读

我们继续总结学习Java基础知识,温故知新。

本文涉及知识点:
AQS - AbstractQueuedSynchronizer
CAS(Compare And Swap)
锁概念 volatile
ReentrantLock

二、概览

一、是什么
CyclicBarrier是JDK提供的一个同步工具,它的作用是让一组线程全部达到一个状态之后再全部同时执行。
特点:
所有线程执行完毕之后是可以重用的。

二、实现原理
CyclicBarrier就是利用了 AQS - AbstractQueuedSynchronizer 的共享锁来实现。
调用await的线程会被阻塞,直到计数器为0,在继续执行。
内部维护parties记录总线程数,count用于计数,最开始count=parties,调用await()之后count原子递减,当count为0之后,再次将parties赋值给count,这就是复用的原理

三、使用场景
一组线程全部达到一个状态之后再全部同时执行。
eg:
多线程计算结果,最后在合并结果。

四、优劣

  • 优点 :代码优雅,不需要对线程池进行操作,将线程池作为 Bean 的情况下有很好的使用场景。
  • 缺点 :需要提前知道线程数量;性能确实,呃呃呃呃呃,差了点。哦对了,还需要在线程代码块内加上异常判断,否则在 countDown 之前发生异常而没有处理,就会导致主线程永远阻塞在 await。
    需要提前知道数量。

需要注意异常的处理,await() 跟 num 必须成对出现,否则线程会一直阻塞。

CyclicBarrier 和 countdownlatch 的区别

CyclicBarrier和CountDownLatch都是多线程同步工具,但是它们的工作机制和用途略有不同:
CyclicBarrier更适合于需要循环等待多个线程的场景,而CountDownLatch则更适合于需要等待特定数量线程完成的场景。

  • 构造和计数机制
    CountDownLatch在构造时需要一个初始计数值,每调用一次countDown()方法,计数器减1,当计数器达到0时,await()方法会释放等待线程。CyclicBarrier则不需要初始计数值,它通过在每个线程中设置一个屏障(barrier)来阻塞线程,直到所有线程都到达屏障后才会释放。

  • 线程状态
    CountDownLatch不会改变线程状态,只是让线程等待,而CyclicBarrier在屏障未达到时阻塞线程,达到屏障后才解除阻塞。

  • 重用性
    CyclicBarrier是可重用的,在所有线程都通过屏障后,可以再次设置新的屏障,而CountDownLatch是一次性的。

  • 等待线程数
    CountDownLatch等待的线程数是固定的,而CyclicBarrier可以等待任意数量的线程

countdownlatch 是一个线程等待其他线程执行完毕后再执行,
CyclicBarrier 是每一个线程等待所有线程执行完毕后,再执行

三、使用

从字面上的意思可以知道,这个类的中文意思是“循环栅栏”。大概的意思就是一个可循环利用的屏障。
它的作用就是会让所有线程都等待完成后才会继续下一步行动。
现实生活中我们经常会遇到这样的情景,在进行某个活动前需要等待人全部都齐了才开始。例如吃饭时要等全家人都上座了才动筷子,旅游时要等全部人都到齐了才出发,比赛时要等运动员都上场后才开始。

CyclicBarrier叫做回环屏障,它的作用是让一组线程全部达到一个状态之后再全部同时执行,而且他有一个特点就是所有线程执行完毕之后是可以重用的。

public class CyclicBarrierTest {
    private static int num = 3;
    private static CyclicBarrier cyclicBarrier = new CyclicBarrier(num, () -> {
        System.out.println("---------所有人都好了, await 次数到 ----------");
    });
    private static ExecutorService executorService = Executors.newFixedThreadPool(num);

    public static void main(String[] args) throws Exception{

        test2Cyclic();
//        test2Cyclic1();
        executorService.shutdown();
    }

    public static void test2Cyclic1() {
        executorService.submit(() -> {
            System.out.println("A在上厕所");
            try {
                Thread.sleep(4000);
                System.out.println("A上完了");
                cyclicBarrier.await();
                System.out.println("会议结束,A退出");
            } catch (Exception e) {
                e.printStackTrace();
            }finally {

            }
        });
        executorService.submit(()->{
            System.out.println("B在上厕所");
            try {
                Thread.sleep(2000);
                System.out.println("B上完了");
                cyclicBarrier.await();
                System.out.println("会议结束,B退出");
            } catch (Exception e) {
                e.printStackTrace();
            }finally {

            }
        });
        executorService.submit(()->{
            System.out.println("C在上厕所");
            try {
                Thread.sleep(3000);
                System.out.println("C上完了");
                cyclicBarrier.await();
                System.out.println("会议结束,C退出");
            } catch (Exception e) {
                e.printStackTrace();
            }finally {

            }
        });
    }

    public static void test2Cyclic() {
        executorService.submit(() -> {
            System.out.println("A在上厕所");
            try {
                Thread.sleep(4000);
                System.out.println("A上完了");
                cyclicBarrier.await();
                System.out.println("会议结束,A退出,开始撸代码");
                cyclicBarrier.await();
                System.out.println("C工作结束,下班回家");
                cyclicBarrier.await();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {

            }
        });
        executorService.submit(() -> {
            System.out.println("B在上厕所");
            try {
                Thread.sleep(2000);
                System.out.println("B上完了");
                cyclicBarrier.await();
                System.out.println("会议结束,B退出,开始摸鱼");
                cyclicBarrier.await();
                System.out.println("B摸鱼结束,下班回家");
                cyclicBarrier.await();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {

            }
        });
        executorService.submit(() -> {
            System.out.println("C在上厕所");
            try {
                Thread.sleep(3000);
                System.out.println("C上完了");
                cyclicBarrier.await();
                System.out.println("会议结束,C退出,开始摸鱼");
                cyclicBarrier.await();
                System.out.println("C摸鱼结束,下班回家");
                cyclicBarrier.await();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {

            }
        });

        executorService.shutdown();

    }
}

四、原理

子类的任务有:

  1. 通过CAS操作维护共享变量state。
  2. 重写资源的获取方式。
  3. 重写资源释放的方式。

在这里插入图片描述

一起看下代码

// 1、 创建CountDownLatch并设置计数器值,count代表计数器个数(内部是共享锁,本质就是上了几次锁)
第二个构造方法有一个 Runnable 参数,这个参数的意思是最后一个到达线程要做的任务
public CyclicBarrier(int parties, Runnable barrierAction) 

// 2、子线程调用await()方法时,获取独占锁,同时对count递减,进入阻塞队列,然后释放锁
// 当第一个线程被阻塞同时释放锁之后,其他子线程竞争获取锁,操作同1
public void await()

// 等待一定时间
public void await(long timeout, TimeUnit unit)

//同步锁
    private final ReentrantLock lock = new ReentrantLock();

    //条件队列
    private final Condition trip = lock.newCondition();

CyclicBarrier借助ReentranLock与Condition来对线程进行阻塞的

private int dowait(boolean timed, long nanos)
        throws InterruptedException, BrokenBarrierException,
               TimeoutException {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            //当前代
            final Generation g = generation;
            //判断当前代的状态,如果当前代后的屏障被打破,则g.broken返回true,否则返回false。
            if (g.broken)
                throw new BrokenBarrierException();
            //判断当前线程是否被中断
            if (Thread.interrupted()) {
                //如果当前线程已经被中断,则调用breakBarrier()
                //该方法代码为generation.broken = true;count = parties;trip.signalAll();
                //可见,只做了3件事:先将当前代的屏障变为打破状态,接着重置计数器的值,最后唤醒所有被阻塞的线程
                breakBarrier();
                //最后抛出中断异常
                throw new InterruptedException();
            }
            //将计数器的值减1
            int index = --count;
            if (index == 0) {
                //如果当前计数器的值为0
                boolean ranAction = false;
                try {
                    //则先执行换代任务,可以看得出来,是由最后一个到达屏障的线程执行的
                    final Runnable command = barrierCommand;
                    if (command != null)
                        command.run();
                    ranAction = true;
                    //开启下一代,这个方法的代码为trip.signalAll();count = parties;generation = new Generation();
                    //该代码唤醒所有被阻塞的线程,重置计数器的值,并且实例化下一代
                    nextGeneration();
                    return 0;
                } finally {
                    //如果换代任务未执行成功,则先将当前代的屏障变为打破状态,接着重置计数器的值,最后唤醒所有被阻塞的线程
                    if (!ranAction)
                        breakBarrier();
                }
            }

            //当前线程一直阻塞,直到“有parties个线程到达barrier” 或 “当前线程被中断” 或 “超时”这3者之一发生
            //死循环
            for (;;) {
                try {
                    if (!timed)
                        //如果不是定时等待,则调用条件队列的await()进行阻塞
                        trip.await();
                    else if (nanos > 0L)
                        //如果是定时等待,则调用条件队列的awaitNanos进行等待
                        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();

                //如果是正常换代,则返回index值
                if (g != generation)
                    return index;

                //如果是定时等待,且时间已经到了,则打破屏障,唤醒所有阻塞的线程,最后抛出异常
                if (timed && nanos <= 0L) {
                    breakBarrier();
                    throw new TimeoutException();
                }
            }
        } finally {
            lock.unlock();
        }
    }
  1. 当子线程调用await()方法时,获取独占锁,同时对count递减,进入阻塞队列,然后释放锁
  2. 当第一个线程被阻塞同时释放锁之后,其他子线程竞争获取锁,操作同1
  3. 直到最后count为0,执行CyclicBarrier构造函数中的任务,执行完毕之后子线程继续向下执行

五、 推荐阅读

Java 专栏

SQL 专栏

数据结构与算法

Android学习专栏

在这里插入图片描述

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

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

相关文章

数据结构与算法—双向链表

目录 一、链表的分类 二、双向链表原理 三、实现双向链表 1、声明链表结构体 2、初始化链表 3、创建新节点 4、打印链表 5、头插&尾插 头插 尾插 6、头删&尾删 头删 尾删 7、 查找节点 8、指定节点前插入 9、删除指定节点 10、销毁链表 完整版 L…

Java三层架构、表现层-业务层-持久层

三层架构 什么是 Java 三层架构 三层架构是指&#xff1a;视图层view&#xff08;表现层&#xff09;&#xff0c;服务层service&#xff08;业务逻辑层&#xff09;&#xff0c;持久层Dao&#xff08;数据访问层&#xff09;&#xff0c; Java的三层架构是指将Java程序分为三…

会议剪影 | 思腾合力携AI服务器亮相PRCV 2023,并作主题演讲

第六届中国模式识别与计算机视觉大会&#xff08;PRCV 2023&#xff09;于2023年10月13日至15日在厦门国际会议中心酒店举办。本届会议主题为“相约鹭岛&#xff0c;启智未来”。 会议旨在汇聚国内国外模式识别和计算机视觉理论与应用研究的广大科研工作者及工业界同行&#xf…

【LeetCode刷题(数据结构与算法)】:将二叉搜索树转化为排序的双向链表

将一个 二叉搜索树 就地转化为一个 已排序的双向循环链表 对于双向循环列表&#xff0c;你可以将左右孩子指针作为双向循环链表的前驱和后继指针&#xff0c;第一个节点的前驱是最后一个节点&#xff0c;最后一个节点的后继是第一个节点 特别地&#xff0c;我们希望可以 就地 完…

pip 时报错 no such option: --bulid-dir 的解决办法

Pycharm 安装第三方库报错及解决方案——no such option: --build-dir Pycharm 安装第三方库报错及解决方案——no such option: --build-dir 最近在学习路径规划相关内容&#xff0c;在运行GitHub上下载例程时缺少“plotly”库&#xff0c;根据网上查到的安装步骤操作&#x…

计算机组成原理 new08 电路 $\color{red}{Δ}$

文章目录 ALU基本逻辑运算复合逻辑的运算 一位全加器串行加法器串行进位的并行加法器并行进位的并行加法器(全先行进位加法器)这个明天再写。加法电路原理总结ALU和加法器有什么关系加法器原理ALU总结无符号整数/补码加减法加法器标志位的生成补码加减法发运算的溢出判断溢出电…

Python实现一个简单的http服务,Url传参输出html页面

摘要 要实现一个可以接收参数的HTTP服务器&#xff0c;您可以使用Python标准库中的http.server模块。该模块提供了一个简单的HTTP服务器&#xff0c;可以用于开发和测试Web应用程序。 下面是一个示例代码&#xff0c;它实现了一个可以接收参数的HTTP服务器&#xff1a; 代码…

C1N短网址 - 是如何做到行业领先的

今天从技术角度来聊下短网址的一些事情&#xff0c;市面上的短网址发展基本上经历了几个阶段。 短网址发展的几个阶段&#xff1a; 第一阶段&#xff1a;网址缩短&#xff0c;很纯粹的功能&#xff0c;各个大小公司都在做&#xff0c;门槛很低。典型代表&#xff1a;百度短网…

Python+playwright 实现Web UI自动化

实现Web UI自动化 技术&#xff1a;Pythonplaywright 目标&#xff1a;自动打开百度浏览器&#xff0c;并搜索“亚运会 金牌榜” 需安装&#xff1a;Playwright &#xff08;不用安装浏览器驱动&#xff09; # 使用浏览器&#xff0c;并可视化打开 browser playwright.ch…

Power BI 傻瓜入门 3. 选择Power BI的版本

本章内容包括&#xff1a; Excel与Power BI的比较选择Power BI的桌面版和服务版之间的差异了解Microsoft提供的许可选项 挑选正确版本的Power BI可能就像参观世界上最大的糖果店&#xff1a;你可以从许多细微差别的替代品中进行选择。选择可以归结为想要、需要、规模&#xf…

用HFSS仿真平面线圈的电感量

用HFSS工具仿真平面线圈的电感量 平面线圈是指在平面上绕制而成的线圈&#xff0c;如PCB上的电感线圈、无线供电使用的金属丝绕制而成的线圈等。根据线圈的不同形状可将平面线圈分为方形线圈&#xff0c;六角形线圈、八角形线圈、螺旋原型线圈等。 网络上的计算平面线圈电感量…

Tmux:终端复用器的基本使用(二)

相关阅读 Tmuxhttps://blog.csdn.net/weixin_45791458/category_12472796.html?spm1001.2014.3001.5482 上一篇文章列举了一些关于tmux中会话的基本使用方法&#xff0c;但会话并非是tmux的最强大的功能&#xff0c;tmux还能在一个会话中创建多个窗口(windows)&#xff0c;并…

springboot配置打野sql语句,而不打印结果

application.yml&#xff0c;注释掉mybatis-plus&#xff0c;增加logging&#xff0c;级别debug

【暴力剪枝】CF1708D

https://codeforces.com/contest/1708/problem/D 题意 思路 这样的操作下&#xff0c;数列减的速度是非常快的&#xff0c;也就是说&#xff0c;易出现很多的0&#xff0c;0的操作没啥意义&#xff0c;所以我们要找到第一个 >0 的数对其后的序列进行排序&#xff0c;就能大…

Vue中的路由是如何工作的?Vue Router的基本用法。

在Vue中,路由是用于管理应用程序中不同页面之间导航的机制。 Vue Router是Vue.js官方提供的路由管理器,它通过集成到Vue应用程序中,提供了一种简单而强大的方式来实现单页应用程序(SPA)的路由功能。 以下是Vue Router的基本用法: 1:安装Vue Router: 首先,使用npm或…

二维码智慧门牌管理系统升级解决方案:地图展示

文章目录 前言一、地图展示功能二、其他升级和改进 前言 随着城市的发展和信息化建设的推进&#xff0c;二维码智慧门牌管理系统在社区管理、物流配送、巡检巡查等多个领域发挥着越来越重要的作用。为了更好地满足用户需求&#xff0c;提升管理效率和服务质量&#xff0c;我们…

【JavaEE重点知识归纳】第9节:抽象类和接口

目录 一&#xff1a;抽象类 1.概念 2.语法 3.特性 4.作用 二&#xff1a;接口 1.概念 2.语法 3.接口使用 4.特性 5.实现多个接口 6.接口间的继承 7.Comparable接口 8.Clonable接口 9.抽象类和接口的区别 一&#xff1a;抽象类 1.概念 &#xff08;1&#xff0…

【C++】类型转换(dynamic_cast,const_cast,static_cast,reinterpret_cast)

&#x1f30f;博客主页&#xff1a; 主页 &#x1f516;系列专栏&#xff1a; C ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ &#x1f60d;期待与大家一起进步&#xff01; 文章目录 C语言中的类型转换一、static_cast二、reinterpret_cast三、 const_cast四、 dynamic…

xlive.dll下载安装方法分享,教你快速修复xlive.dll文件

在运行某些应用程序或游戏时&#xff0c;你可能会遭遇到"xlive.dll缺失"错误提示&#xff0c;这可能导致程序无法正常运行。本文将向你介绍一些可行的解决方法教你下载xlive.dll文件&#xff0c;并详细阐述xlive.dll是什么文件以及导致其缺失的原因。 一.理解"x…

2023-10-19 指针与指针的指针,我就不信你脑壳不疼

点击 <C 语言编程核心突破> 快速C语言入门 指针与指针的指针&#xff0c;我就不信你脑壳不疼 前言一、从一个链表实现说起二、指针, 指针的指针, 头疼的来源总结 前言 C实现一个链表&#xff0c;为什么有时候传入指针&#xff0c;有时候传入指针的指针&#xff0c;究竟…