Java并发类的主要API方法-CountDownLatch和CyclicBarrier

news2024/11/16 22:25:18

1.概念介绍

CountDownLatch 是一个计数器,计数器的初始值由创建它时指定。每次调用 countDown() 方法时,计数器会减1,直到计数器值变为0时,所有调用 await() 的线程都会被唤醒继续执行。

CyclicBarrier 是 Java 中另一个常用的同步辅助工具,它允许一组线程互相等待,直到所有线程都达到一个共同的屏障点(barrier)。与 CountDownLatch 不同的是,CyclicBarrier 可以被重用,即当所有线程都达到了屏障点并执行完相应任务后,CyclicBarrier 可以再次使用。CyclicBarrier 的主要优势在于它的重用性和灵活性,特别适用于循环性或分阶段的任务场景。

重用:CyclicBarrier 可以重用,而 CountDownLatch 是一次性的,计数器不能重置。

目的:CountDownLatch 用于让一个或多个线程等待其他线程完成,而 CyclicBarrier 是让一组线程互相等待,直到所有线程都到达一个屏障点。

2. 简单举例

1) CountDownLatch 简单示例:
import java.util.concurrent.CountDownLatch;

public class CountDownLatchExample {
    public static void main(String[] args) throws InterruptedException {
        int numberOfTasks = 3;
        CountDownLatch latch = new CountDownLatch(numberOfTasks);

        // 创建并启动任务
        for (int i = 0; i < numberOfTasks; i++) {
            new Thread(new Task(latch)).start();
        }

        // 主线程等待所有任务完成
        latch.await();

        System.out.println("所有任务已完成,继续主线程的操作。");
    }
}

class Task implements Runnable {
    private CountDownLatch latch;

    public Task(CountDownLatch latch) {
        this.latch = latch;
    }

    @Override
    public void run() {
        try {
            // 模拟任务执行时间
            Thread.sleep((long) (Math.random() * 1000));
            System.out.println(Thread.currentThread().getName() + " 任务完成");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            latch.countDown(); // 任务完成后计数器减1
        }
    }
}

解释

• CountDownLatch latch = new CountDownLatch(3);:初始化 CountDownLatch,计数器值为3。

• 每个线程在执行完任务后调用 latch.countDown();,计数器减1。

• latch.await(); 让主线程等待,直到计数器变为0,也就是所有任务完成。

CountDownLatch 非常适合用在需要等多个线程执行完毕后再执行下一步操作的场景,比如并行任务的同步。

2) CountDownLatch 有漏洞的比赛示例(有漏洞):

本实验想要实现的是裁判员要等待所有运动员各就各位后全部准备完毕,再开始比赛的效果。
创建测试用的项目代码如下:

package service;

import java.util.concurrent.CountDownLatch;

public class MyService {

    private CountDownLatch down = new CountDownLatch(1);

    public void testMethod() {
        try {
            System.out.println(Thread.currentThread().getName() + "准备");
            down.await();
            System.out.println(Thread.currentThread().getName() + "结束");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void downMethod() {
        System.out.println("开始");
        down.countDown();
    }

}
线程类MyThread.java代码如下:
package extthread;

import service.MyService;

public class MyThread extends Thread {

    private MyService myService;

    public MyThread(MyService myService) {
        super();
        this.myService = myService;
    }

    @Override
    public void run() {
        myService.testMethod();
    }

}
运行类Run.java代码如下:
package test.run;

import service.MyService;
import extthread.MyThread;

public class Run {
    public static void main(String[] args) throws InterruptedException {
        MyService service = new MyService();
        MyThread[] tArray = new MyThread[10];
        for (int i = 0; i < tArray.length; i++) {
            tArray[i] = new MyThread(service);
            tArray[i].setName("线程" + (i + 1));
            tArray[i].start();
        }
        Thread.sleep(2000);
        service.downMethod();
    }
}
程序运行结果如下图所示:

此实验虽然运行成功,但并不能保证在main主线程中执行了service.downMethod();时,所有的工作线程都呈wait状态,因为某些线程有可能准备的时间花费较长,可能耗用的时间超过2秒,这时如果在第2秒时调用service.downMethod();方法就达不到“唤醒所有线程”继续向下运行的目的了,也就是说裁判员没有等全部的运动员到来时,就让发令枪响起开始比赛了,这是不对的,所以就需要对代码进行修改,来达到相对比较完善的比赛流程。

漏洞:countDown()方法的调用时机在所有任务完成任务的时间点之前,导致问题。

改进:等待10个线程要用CountDownLatch(10), 或者用CyclicBarrier。

2) CountDownLatch 完整的比赛示例:

使用CountDownLatch类来实现“所有的线程”呈wait后再统一唤醒的效果,通过大量使用CountDownLatch类来实现业务要求的同步效果。
创建实验用的项目,代码如下:

package extthread;

import java.util.concurrent.CountDownLatch;

public class MyThread extends Thread {

    private CountDownLatch comingTag;   // 裁判等待所有运动员到来
    private CountDownLatch waitTag;        // 等待裁判说准备开始
    private CountDownLatch waitRunTag;  // 等待起跑
    private CountDownLatch beginTag;      // 起跑
    private CountDownLatch endTag;        // 所有运动员到达终点

    public MyThread(CountDownLatch comingTag, CountDownLatch waitTag,
            CountDownLatch waitRunTag, CountDownLatch beginTag,
            CountDownLatch endTag) {
        super();
        this.comingTag = comingTag;
        this.waitTag = waitTag;
        this.waitRunTag = waitRunTag;
        this.beginTag = beginTag;
        this.endTag = endTag;
    }

    @Override
    public void run() {
        try {
            System.out.println("运动员使用不同交通工具不同速度到达起跑点,正向这头走!");
            Thread.sleep((int) (Math.random() * 10000));
            System.out.println(Thread.currentThread().getName() + "到起跑点了!");
            comingTag.countDown();
            System.out.println("等待裁判说准备!");
            waitTag.await();
            System.out.println("各就各位!准备起跑姿势!");
            Thread.sleep((int) (Math.random() * 10000));
            waitRunTag.countDown();
            beginTag.await();
            System.out.println(Thread.currentThread().getName()
                    + " 运行员起跑 并且跑赛过程用时不确定");
            Thread.sleep((int) (Math.random() * 10000));
            endTag.countDown();
            System.out.println(Thread.currentThread().getName() + " 运行员到达终点");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}
运行类Run.java代码如下:
package test;

import java.util.concurrent.CountDownLatch;
import extthread.MyThread;

public class Run {

    public static void main(String[] args) {
        try {
            CountDownLatch comingTag = new CountDownLatch(10);
            CountDownLatch waitTag = new CountDownLatch(1);
            CountDownLatch waitRunTag = new CountDownLatch(10);
            CountDownLatch beginTag = new CountDownLatch(1);
            CountDownLatch endTag = new CountDownLatch(10);

            MyThread[] threadArray = new MyThread[10];
            for (int i = 0; i < threadArray.length; i++) {
                threadArray[i] = new MyThread(comingTag, waitTag, waitRunTag,
                        beginTag, endTag);
                threadArray[i].start();
            }
            System.out.println("裁判员在等待选手的到来!");
            comingTag.await();
            System.out.println("裁判看到所有运动员来了,各就各位前“巡视”用时5秒");
            Thread.sleep(5000);
            waitTag.countDown();
            System.out.println("各就各位!");
            waitRunTag.await();
            Thread.sleep(2000);
            System.out.println("发令枪响起!");
            beginTag.countDown();
            endTag.await();
            System.out.println("所有运动员到达,统计比赛名次!");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

}

运动员使用不同交通工具不同速度到达起跑点,正向这头走!
运动员使用不同交通工具不同速度到达起跑点,正向这头走!
运动员使用不同交通工具不同速度到达起跑点,正向这头走!
运动员使用不同交通工具不同速度到达起跑点,正向这头走!
运动员使用不同交通工具不同速度到达起跑点,正向这头走!
运动员使用不同交通工具不同速度到达起跑点,正向这头走!
运动员使用不同交通工具不同速度到达起跑点,正向这头走!
运动员使用不同交通工具不同速度到达起跑点,正向这头走!
运动员使用不同交通工具不同速度到达起跑点,正向这头走!
裁判员在等待选手的到来!
运动员使用不同交通工具不同速度到达起跑点,正向这头走!
Thread-9到起跑点了!
等待裁判说准备!
Thread-2到起跑点了!
等待裁判说准备!
Thread-7到起跑点了!
等待裁判说准备!
Thread-5到起跑点了!
等待裁判说准备!
Thread-4到起跑点了!
等待裁判说准备!
Thread-0到起跑点了!
等待裁判说准备!
Thread-1到起跑点了!
等待裁判说准备!
Thread-3到起跑点了!
等待裁判说准备!
Thread-8到起跑点了!
等待裁判说准备!
Thread-6到起跑点了!
等待裁判说准备!
裁判看到所有运动员来了,各就各位前“巡视”用时5秒
各就各位!
各就各位!准备起跑姿势!
各就各位!准备起跑姿势!
各就各位!准备起跑姿势!
各就各位!准备起跑姿势!
各就各位!准备起跑姿势!
各就各位!准备起跑姿势!
各就各位!准备起跑姿势!
各就各位!准备起跑姿势!
各就各位!准备起跑姿势!
各就各位!准备起跑姿势!
发令枪响起!
Thread-9 运行员起跑 并且跑赛过程用时不确定
Thread-1 运行员起跑 并且跑赛过程用时不确定
Thread-4 运行员起跑 并且跑赛过程用时不确定
Thread-7 运行员起跑 并且跑赛过程用时不确定
Thread-8 运行员起跑 并且跑赛过程用时不确定
Thread-5 运行员起跑 并且跑赛过程用时不确定
Thread-0 运行员起跑 并且跑赛过程用时不确定
Thread-2 运行员起跑 并且跑赛过程用时不确定
Thread-3 运行员起跑 并且跑赛过程用时不确定
Thread-6 运行员起跑 并且跑赛过程用时不确定
Thread-2 运行员到达终点
Thread-8 运行员到达终点
Thread-4 运行员到达终点
Thread-5 运行员到达终点
Thread-9 运行员到达终点
Thread-7 运行员到达终点
Thread-6 运行员到达终点
Thread-3 运行员到达终点
Thread-0 运行员到达终点
Thread-1 运行员到达终点
所有运动员到达,统计比赛名次!
4) CyclicBarrier 简单示例:

每次当所有线程都到达屏障点(即调用 await()),屏障就会被“破坏”,所有等待的线程继续执行,CyclicBarrier 会自动重置为初始状态,允许它再次被使用。这个过程可以无限次重复。

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierReuseExample {
    public static void main(String[] args) {
        int numberOfThreads = 3;
        CyclicBarrier barrier = new CyclicBarrier(numberOfThreads, new Runnable() {
            @Override
            public void run() {
                System.out.println("所有线程都已到达屏障点,继续下一阶段...");
            }
        });

        // 启动3个线程,它们将经过5个阶段,每个阶段有3个屏障点
        for (int i = 0; i < numberOfThreads; i++) {
            new Thread(new Task(barrier)).start();
        }
    }
}

class Task implements Runnable {
    private CyclicBarrier barrier;

    public Task(CyclicBarrier barrier) {
        this.barrier = barrier;
    }

    @Override
    public void run() {
        try {
            for (int i = 0; i < 5; i++) { // 让线程经过5个阶段,模拟5个阶段
                System.out.println(Thread.currentThread().getName() + " 正在执行阶段 " + (i + 1) + "...");
                Thread.sleep((long) (Math.random() * 1000)); // 模拟任务处理时间

                System.out.println(Thread.currentThread().getName() + " 到达屏障点 " + (i + 1));
                barrier.await(); // 等待其他线程到达屏障点

                // 继续执行下一阶段
            }
        } catch (InterruptedException | BrokenBarrierException e) {
            e.printStackTrace();
        }
    }
}
Thread-0 正在执行阶段 1...
Thread-1 正在执行阶段 1...
Thread-2 正在执行阶段 1...
Thread-0 到达屏障点 1
Thread-1 到达屏障点 1
Thread-2 到达屏障点 1
所有线程都已到达屏障点,继续下一阶段...
Thread-2 正在执行阶段 2...
Thread-1 正在执行阶段 2...
Thread-0 正在执行阶段 2...
...
所有线程都已到达屏障点,继续下一阶段...
Thread-0 正在执行阶段 5...
Thread-1 正在执行阶段 5...
Thread-2 正在执行阶段 5...
Thread-2 到达屏障点 5
Thread-0 到达屏障点 5
Thread-1 到达屏障点 5
所有线程都已到达屏障点,继续下一阶段...

解释:

重用:for (int i = 0; i < 5; i++) 循环中,每个线程都会经过5次屏障点。每当所有线程都到达屏障点并通过时,CyclicBarrier 会自动重置,使得它可以在下一轮循环中继续使用。

屏障点:在每个阶段,所有线程都需要等到其他线程都到达相同的屏障点,然后再继续执行。这模拟了多阶段任务中同步的场景。

线程同步:在每个屏障点,线程都调用 barrier.await() 来等待其他线程,直到所有线程都到达,这时屏障才会被解除,线程们才继续执行。

自动重置:在每个阶段的屏障点解除后,CyclicBarrier 自动重置,以便在下一个阶段再次使用。

通过这个例子可以看出,CyclicBarrier 的重用特性非常适合于那些需要分阶段执行的任务,确保每个阶段都在所有线程同步完成后再开始下一个阶段。

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

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

相关文章

基于CDIO概念的人工智能物联网系统开发与实施的人才培养研究

目录 1. 引言&#xff08;Introduction&#xff09; 2. AIoT技术及其培训特点&#xff08;The Characteristics of AIOT and Its Training&#xff09; 3. 基于CDIO概念的AIoT课程改革&#xff08;CDIO Concept-based Reform of AIOT Course&#xff09; 4. AIoT课程内容安…

SweetAlert2

1. SweetAlert2 SweetAlert2是一个基于JavaScript的库, 用于在网页上替换标准的警告框(alert), 确认框(confirm)和提示框(prompt), 并提供更加美观和用户友好的界面.需要在项目中引入SweetAlert2, 可以通过CDN链接或者将库文件下载到你的项目中来实现这一点. 通过CDN引入:<…

C++:stack类(vector和list优缺点、deque)

目录 前言 数据结构 deque vector和list的优缺点 push pop top size empty 完整代码 前言 stack类就是数据结构中的栈 C数据结构&#xff1a;栈-CSDN博客 stack类所拥有的函数相比与string、vector和list类都少很多&#xff0c;这是因为栈这个数据结构是后进先出的…

SPRING09_ Bean后置处理器创建过程、SmartInstantiationAwareBeanPostProcessor预测方法调用

文章目录 ①. Bean后置处理器创建过程②. SmartInstantiationAwareBeanPostProcessor预测方法调用 ①. Bean后置处理器创建过程 ①. 坏境准备,在BeanPostProcessor的无参构造器、postProcessBeforeInitialization以及postProcessAfterInitialization打上断点.以xml的方式启动容…

秋招突击——8/15——新作{最大子数组和、合并区间、转轮数组、除自身以外的数组的乘积}

文章目录 引言新作最大子数组和个人实现参考实现 合并区间个人实现短板补充——自定义排序标准 参考实现 转轮数组最终实现 除自身以外数组的乘积个人实现 总结 引言 以前刷题的方式方法有问题&#xff0c;花太多时间了&#xff0c;应该先过一遍&#xff0c;然后再针对特定的题…

第一百九十四节 Java集合教程 - Java优先级队列

Java集合教程 - Java优先级队列 优先级队列是其中每个元素具有相关联的优先级的队列。具有最高优先级的元素将从队列中删除。 PriorityQueue 是一个实现类对于Java Collection Framework中的无界优先级队列。 我们可以使用在每个元素中实现的 Comparable 接口作为其优先事项。…

C# OnnxRuntime YoloV5 Demo

目录 效果 模型信息 项目 代码 Form1.cs YoloV5.cs 下载 效果 模型信息 Model Properties ------------------------- --------------------------------------------------------------- Inputs ------------------------- name&#xff1a;images tensor&#xff1a…

机器学习/人工智能中的学习证明

一、说明 在进行任何数学发展之前&#xff0c;我们必须首先了解学习的基础以及它如何与错误的概念密切相关。关于代价函数&#xff0c;它的工作原理是梯度下降原理。本文将回顾梯度下降原理。 二、假想的厨师 想象一下&#xff0c;在任何一天&#xff0c;你决定复制你在一家著名…

8.13 Day19 Windows服务器(Windows service 2008 R2)上域的搭建 (1)

域服务器&#xff08;DC&#xff09;&#xff1a;安装了活动目录服务的服务器就称为DC。 将三台设备配置在同一网络中&#xff0c;此处将外部网络隔离开&#xff0c;只将他们放在局域网中 服务端网络配置&#xff0c;此时与外部网络彻底隔绝开&#xff0c;且已无法和主机通信&…

XSS game复现(DOM型)

目录 1.Ma Spaghet! 2.Jefff 3.Ugandan Knuckles 4.Ricardo Milos 5.Ah Thats Hawt 6.Ligma 7.Mafia 8.Ok, Boomer 1.Ma Spaghet! 通过简单的尝试发现传递参数可以直接进入h2标签 接下来我们尝试传入一个alert(1) 可以看到并没有触发。原因是在innerHTML中官方禁用了sc…

二进制安装php

下载php二进制包&#xff1a; 官网地址&#xff1a;https://www.php.net/releases/ PHP: Releaseshttps://www.php.net/releases/在里边可以选择自己要下载的包进行下载&#xff1b; 下载完成后进行解压&#xff1a; tar xvzf php-7.3.12.tar.gz 解压后 进入目录进行预编…

xss案例

首先进入XSS Game - Learning XSS Made Simple! | Created by PwnFunction打开环境 Ma Spaghet 在script里面给使用get传参给somdbody传一个值&#xff0c;若没有传值&#xff0c;默认传SomebodyToucha Ma Spaghet!,赋值给spaghet,放在h2标签中&#xff0c;spaghet后会有一个in…

Linux根目录下的各个目录的用途介绍

在Linux系统中&#xff0c;我们可以通过cd /命令进入根目录&#xff0c;然后ls -l(或者ll命令)即可查看根下目前的目录情况&#xff1a; 这些不同目录的用途说明如下&#xff1a; /bin&#xff1a;包含基本命令文件&#xff0c;如ls、cp等&#xff0c;这个文件中的文件都是可执…

基于协同过滤算法的黔醉酒业白酒销售系统_p091v--论文

TOC springboot349基于协同过滤算法的黔醉酒业白酒销售系统_p091v--论文 绪论 1.1背景及意义 中国经济快速发展&#xff0c;人均GDP逐年上涨&#xff0c;非生活必须品的消费比重也随之增加 &#xff0c;酒类销售额度&#xff0c;尤其是酱香型白酒销售额近些年可谓发展迅猛&…

STM32通过I2C硬件读写MPU6050

目录 STM32通过I2C硬件读写MPU6050 1. STM32的I2C外设简介 2. STM32的I2C基本框图 3. STIM32硬件I2C主机发送流程 10位地址与7位地址的区别 7位主机发送的时序流程 7位主机接收的时序流程 4. STM32硬件与软件的波形对比 5. STM32配置硬件I2C外设流程 6. STM32的I2C.h…

Hadoop如何搭建计算和存储节点分离

在业内存在着一种看起来比较离谱的搭建方式&#xff0c;叫计算节点与存储节点分离&#xff0c;说它比较离谱&#xff0c;是因为hadoop架构本身不直接支持将这两者分开&#xff0c;因为hadoop本身的一大优势就是计算本地化&#xff0c;这种分开搭建的方式抛弃了这种优势&#xf…

Linux 软件编程学习第十五天

1.TCP粘包问题&#xff1a; TCP发送数据是连续的&#xff0c;两次发送的数据可能粘连成一包被接收到 1.解决粘包问题方法&#xff1a; 1.接收指定长度&#xff1a;&#xff08;不稳定&#xff09; 发送5个字节 接收5个字节 2.睡眠&#x…

用户画像实时标签数据处理流程图

背景 在用户画像中&#xff0c;有一类实时标签&#xff0c;我们既要它能够实时的对外提供数据统计&#xff0c;也要保存到大数据组件中用于后续的对数&#xff0c;圈选的逻辑&#xff0c;本文就看一下用户画像的实时标签的数据流转图 实时标签数据流转图 首先我们肯定是要使…

GoMail发送邮件的性能优化策略有哪些方法?

GoMail发送邮件如何配置服务器&#xff1f;GoMail发信功能如何&#xff1f; GoMail是一款广受欢迎的Go语言邮件发送库&#xff0c;具备高效、易用等优点&#xff0c;但在高并发场景下&#xff0c;GoMail发送邮件的性能优化显得尤为重要。AokSend将探讨几种有效的GoMail发送邮件…

图像数据处理14

三、空域滤波 3.3 统计排序滤波器 统计排序滤波器属于非线性空域滤波器&#xff0c;常见的统计排序滤波器有中值滤波器、最大值滤波器、最小值滤波器。 中值滤波器、最大值滤波器和最小值滤波器是三种常见的统计排序滤波器&#xff0c;它们在图像处理和信号处理中发挥着重要…