深入理解Java 线程并发编排工具: 概述和应用场景

news2024/12/28 20:09:49

目录

    • 前言
    • 概述
      • 1. CountDownLatch
      • 2. CyclicBarrier
      • 3. Semaphore(信号量)
      • 4. Condition
    • 案例
      • CountDownLatch-马拉松场景
      • CyclicBarrier-马拉松场景
      • Semaphore-公交车占座场景
      • Condition-线程等待唤醒场景

前言

在 Java 的 java.util.concurrent (JUC) 包中,提供了四种核心并发工具类:CountDownLatchCyclicBarrierSemaphoreCondition。它们在多线程编程中用于协调线程的执行顺序和资源访问,确保在复杂的并发场景下各任务按照预期顺序和条件完成。通过合理使用这些工具,可以大幅提升程序的可靠性和执行效率。本文将逐一介绍这四种工具的特点和使用场景,帮助大家更好地掌握多线程编程中的关键协作机制。

概述

1. CountDownLatch

等待一个线程或者一组线程全部执行完成后再执行后续的逻辑, 常用于一组任务的并发控制,无法重用。

  • 内部维护一个计数器字段count,使用时需要设置初始数量,表示多少个线程同时执行完成CountDownLatch countDownLatch =new CountDownLatch(3)
  • 常用API
    • countDownLatch.countDown() :每次调用,计数器count减1,直到count为0.
    • countDownLatch.await() :等待阻塞,当count为0时放行。
    • countDownLatch.await(long timeout, TimeUnit unit) :和await()一样,只是可以设置一个超时时间,当等待时间大于超时时间,不管count是否为0,都会跳出阻塞执行后面逻辑
    • countDownLatch.getCount() :获取count计数器数值

2. CyclicBarrier

允许一组线程相互等待,直到它们到达共同的屏障点,再同时继续执行,适用于多线程周期性汇合的场景。不同于 CountDownLatchCyclicBarrier 可重用。

注:对于可重用的解释:当count计数器减为0时,CyclicBarrier 的计数器中的count会自动复原为初始值,继而后面的业务中可再次使用CyclicBarrier对象进行操作,而CountDownLatch不可以。

  • 同样内部也维护了一个count计数器,使用时需要设置初始数量,表示多少个线程同时执行操作CyclicBarrier barrier=new CyclicBarrier(3)
  • 携带回调方法:就是到达屏障点(count =0)后需要额外进行的任务
CyclicBarrier barrier = new CyclicBarrier(3, new Runnable() {
    @Override
    public void run() {
        System.out.println("合照打卡");
    }
});
  • 常用API

    • barrier.await() :CyclicBarrier 中的 await 与 CountDownLatch 的并不一致,CyclicBarrier 没有类似于 countDown 的方法,CyclicBarrier 的 await 方法你可以近似认为它是 CountDownLatch 中 await 和 countDown 的组合。当调用 CyclicBarrier 的 await 方法后,它会阻塞,且将计数器 -1,如果计数器变为 0 后,则跳出等待。不需要显式地减少计数器。
    • barrier. await(long timeout, TimeUnit unit) :该方法在阻塞一段指定的时间后,如果等待的线程未能在超时时间内到达同步点,将抛出 TimeoutException 异常。
    • barrier.getParties() :获取当前count的数值。
    • barrier.reset() :复原count为初始值。
    • barrier.isBroken() :当CyclicBarrier为不可用状态时,返回true。

注:若其中一个线程在等待过程中抛出了 TimeoutException 异常,这将引起其他所有线程在调用 await 时抛出BrokenBarrierException异常。此时,CyclicBarrier 进入不可用状态,必须调用 reset 方法对其进行重置,方可继续使用。这种机制确保在超时或异常情况下,程序能够及时恢复到正常的同步状态。

3. Semaphore(信号量)

是一个用于控制并发访问资源的同步工具,用于控制多个线程对共享资源的并发访问量,通过许可机制允许一定数量的线程同时访问资源,适用于限流、资源池等场景。

  • 内部同样维护了一个计数器字段permits,在semaphore中的定义为令牌,允许指定的线程数量获取到令牌并往下继续执行。成功调用acquire()方法后,permits 减1,直到减为0。Semaphore semaphore =new Semaphore(3)

  • 主要API

    • semaphore.acquire() :获取一个令牌,如果获取到令牌则继续向下执行,没获取到则线程阻塞直到获得令牌为止。
    • semaphore.tryAcquire(long timeout, TimeUnit unit) :获取一个令牌并设置超时时间,在超时时间内获取到令牌返回true,等待时间大于超时时间为获得令牌返回false。
    • semaphore.release() :释放一个令牌,供其他线程使用。
    • semaphore. availablePermits() :返回当前可用的令牌数量。

4. Condition

ReentrantLock 搭配使用,提供比 wait/notify 更灵活的线程等待和通知机制,可实现精准的条件等待和唤醒,常用于复杂线程协作的场景。

private final static ReentrantLock LOCK = new ReentrantLock();
private final static Condition condition = LOCK.newCondition();
  • 主要API

    • condition.await() :线程将锁(就是当前线程通过LOCK.lock()获取的全局锁)交出去(释放全局锁供其他线程获取)并进入阻塞,等待其他线程唤醒。并且当前阻塞可以被中断,并抛出异常 InterruptedException
    • condition.await( int timeout, TimeUnit unit ) :基本与await()方法一样,只是加了一个超时时间,如果等待时间大于超时时间还未被唤醒,跳出阻塞执行后面的逻辑。
    • condition.signal() :随机唤醒一个正在等待的线程。
    • condition.signalAll() :唤醒所有等待中的线程。

注:上面api的调用必须在LOCK.lock()加锁块中使用,否则会抛出IllegalMonitorStateException异常。

案例

源码获取:

GitHub - RemainderTime/xf-solution: 提供一些疑难问题的解决方案

子项目:juc-two

CountDownLatch-马拉松场景

对于CountDownLatch 模拟一个参加马拉松比赛的场景。

在这个马拉松比赛的场景中,假设小明、小刚和小红参加成都马拉松。由于他们的配速不同,所以每个人跑完全程的完成时间也不同,但他们约好必须等到所有人到达终点后一起坐车回家 。

他们跑完全程分别用时:

  • 小明:5s
  • 小刚:6s
  • 小红:7s

创建参数者跑步线程类Running.class

@Slf4j
public class Running implements Runnable {
    //名称
    private String name;
    //用时
    private int time;
    public Running(String name, int time) {
        this.name = name;
        this.time = time;
    }
    @Override
    public void run() {
        log.info(name + "开始跑步了----");
        try {
            Thread.sleep(time);
            log.info(name + "达到终点了----用时:{}秒", time/1000);
            //到达终点后计数器减1
            CountDownLatchMain.countDownLatch.countDown();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

创建执行类CountDownLatchMain.class

@Slf4j
public class CountDownLatchMain {
    //自定义线程池
    private static ThreadPoolExecutor executor = new ThreadPoolExecutor(
        Runtime.getRuntime().availableProcessors(),
        Runtime.getRuntime().availableProcessors(),
        5000,
        TimeUnit.MICROSECONDS,
        new LinkedBlockingDeque());    
    
    //初始化线程编排对象
    public static CountDownLatch countDownLatch =new CountDownLatch(3);
    
    public static void main(String[] args) {
        log.info("马拉松正式开始---");
        try {
            executor.execute(new Running("小明", 5000));
            executor.execute(new Running("小刚", 6000));
            executor.execute(new Running("小红", 7000));
            log.info("等待所有人到达终点---");
            //阻塞等待全部线程完成
            countDownLatch.await();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        log.info("全部到达终点---");
        log.info("一起坐车回家了");
    }
}

执行main() 方法
在这里插入图片描述

CyclicBarrier-马拉松场景

CyclicBarrier同样使用马拉松场景,可以与上面 CountDownLatch进行对照学习他们的相似与不同之处。

为了体现CyclicBarrier的特性,对内容进行了调整

小明、小刚和小红一起参加成都马拉松(设定全程为30公里)。他们配速不同,因此每人跑完10公里的时间各异。他们约定每跑完10公里都要等彼此(可重用特性)到齐后再一起合照打卡(回调函数使用),随后继续比赛,直到抵达终点拍照打卡后各自开车回家。

他们每跑完10公里分别用时:

  • 小明:1s
  • 小刚:2s
  • 小红:3s

创建参数者跑步线程类Running.class

@Slf4j
public class Running implements Runnable {
    //名称
    private String name;
    //用时
    private int time;
    public Running(String name, int time) {
        this.name = name;
        this.time = time;
    }
    @Override
    public void run() {
        log.info(name + "开始跑步了----");
        try {
            Thread.sleep(time);
            log.info(name + "达到10公里了----用时:{}秒", time / 1000);
            //到达终点后计数器减1
            CyclicBarrierMain.barrier.await();
            Thread.sleep(time);
            log.info(name + "达到20公里了----用时:{}秒", time*2 / 1000);
            CyclicBarrierMain.barrier.await();
            Thread.sleep(time);
            log.info(name + "达到终点30公里----用时:{}秒", time*3 / 1000);
            CyclicBarrierMain.barrier.await();
            log.info(name + "独自开车回家了---");
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

创建执行类CyclicBarrierMain.class

@Slf4j
public class CyclicBarrierMain {
    //自定义线程池
    private static ThreadPoolExecutor executor = new ThreadPoolExecutor(
        Runtime.getRuntime().availableProcessors(),
        Runtime.getRuntime().availableProcessors(),
        5000,
        TimeUnit.MICROSECONDS,
        new LinkedBlockingDeque());
    //初始化线程编排对象
    public static CyclicBarrier barrier = new CyclicBarrier(3, new Runnable() {
        @Override
        public void run() {
            System.out.println("合照打卡~~~");
        }});

    public static void main(String[] args) {
        log.info("马拉松正式开始---");
        try {
            executor.execute(new Running("小明", 1000));
            executor.execute(new Running("小刚", 2000));
            executor.execute(new Running("小红", 3000));
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

执行main() 方法
在这里插入图片描述

Semaphore-公交车占座场景

模拟一个坐公交车的场景,当车上没有空位时,站立的人需要等待有人下车了才能获得座位。

小明、小刚、小红、小绿在同一个公交站等同一路公交车。假设公交车只有3个座位,并且前一个站到后面一个站行驶所需要的平均时间为1s,写出他们4人在上车后占座的情况。

4人上车后需要经历的站点数量分别为:

  • 小明:2
  • 小刚:4
  • 小红:4
  • 小绿:4

创建乘客上车行为线程类 Seat.class

@Slf4j
public class Seat implements Runnable {
    //用户名
    private String name;
    //需要经历的站点数量
    private Integer num;
    public Seat(String name, Integer num) {
        this.name = name;
        this.num = num;
    }
    @Override
    public void run() {
        long startTime = System.currentTimeMillis();
        log.info(name + "上车了----");
        try {
            //获取令牌数量
            int tokenCount = SemaphoreMain.semaphore.availablePermits();
            if (tokenCount == 0) {
                log.info(name + "--等待--空缺座位----");
            }
            //获取令牌并设置超时时间
            boolean b = SemaphoreMain.semaphore.tryAcquire(SemaphoreMain.TIME * num, TimeUnit.MILLISECONDS);
            long awaitTime = System.currentTimeMillis();
            if (b) {
                log.info(name + "抢占到了座位----");
                if (tokenCount > 0) {
                    Thread.sleep(SemaphoreMain.TIME * num);
                } else {
                    //等待时间
                    long await = awaitTime - startTime;
                    Thread.sleep(SemaphoreMain.TIME * num - await);
                }
            } else {
                log.info(name + "整个行程没有抢到座位----");
            }
            long endTime = System.currentTimeMillis();
            log.info(name + "到站下车----等待用时:{}秒---占座时长:{}秒", (awaitTime - startTime) / 1000, Math.round((endTime - awaitTime) / 1000.0));
            //释放令牌
            SemaphoreMain.semaphore.release();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

创建公交车运行类 SemaphoreMain.class

@Slf4j
public class SemaphoreMain {
    //自定义线程池
    private static ThreadPoolExecutor executor = new ThreadPoolExecutor(
        4,
        4,
        5000,
        TimeUnit.MICROSECONDS,
        new LinkedBlockingDeque());
    
    //初始化信号量并设置令牌数量
    public static Semaphore semaphore =new Semaphore(3);
    
    //每一个站公交行驶时间 ms
    public static int TIME = 1000;
    
    public static void main(String[] args) {
        log.info("公交车到达站台----");
        executor.execute(new Seat("小明",2));
        executor.execute(new Seat("小刚", 4));
        executor.execute(new Seat("小红",4));
        executor.execute(new Seat("小绿", 4));
    }
}

运行 main() 方法
在这里插入图片描述

Condition-线程等待唤醒场景

简单使用:多个线程进入等待状态,最后的一个线程唤醒所有等待线程

创建等待线程类AwaitThread.class

@Slf4j
public class AwaitThread implements Runnable{
    private String name;
    public AwaitThread(String name) {
        this.name = name;
    }
    @Override
    public void run() {
        ConditionMain.LOCK.lock();
        try {
            log.info("线程{}开始等待-----", name);
            ConditionMain.condition.await();
            log.info("线程{}被唤醒-----", name);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            ConditionMain.LOCK.unlock();
        }
    }
}

创建唤醒线程类SignalThread.class

@Slf4j
public class SignalThread implements Runnable{
    private String name;
    public SignalThread(String name) {
        this.name = name;
    }
    @Override
    public void run() {
        ConditionMain.LOCK.lock();
        try {
            log.info("线程{}开始唤醒其他线程-----", name);
            ConditionMain.condition.signalAll();
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            ConditionMain.LOCK.unlock();
        }
    }
}

创建主线程执行类ConditionMain.class

@Slf4j
public class ConditionMain {
    //自定义线程池
    private static ThreadPoolExecutor executor = new ThreadPoolExecutor(
        4,
        4,
        5000,
        TimeUnit.MICROSECONDS,
        new LinkedBlockingDeque());

    //定义ReentrantLock对象
    public final static ReentrantLock LOCK = new ReentrantLock();
    
    //获取并定义Condition对象
    public final static Condition condition = LOCK.newCondition();
    
    public static void main(String[] args) {
        executor.execute(new AwaitThread("线程1"));
        executor.execute(new AwaitThread("线程2"));
        executor.execute(new AwaitThread("线程3"));
        executor.execute(new SignalThread("线程4"));
    }
}

执行main() 方法
在这里插入图片描述

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

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

相关文章

C++初阶(八)--内存管理

目录 引入: 一、C中的内存布局 1.内存区域 2.示例变量存储位置说明 二、C语言中动态内存管理 三、C内存管理方式 1.new/delete操作内置类型 2.new和delete操作自定义类型 四、operator new与operator delete函数(重要点进行讲解) …

架构的本质之 MVC 架构

前言 程序员习惯的编程方式就是三步曲。 所以,为了不至于让一个类撑到爆💥,需要把黄色的对象、绿色的方法、红色的接口,都分配到不同的包结构下。这就是你编码人生中所接触到的第一个解耦操作。 分层框架 MVC 是一种非常常见且常…

Node学习记录-child_process 子进程

来自:https://juejin.cn/post/7277045020422930488 child_process用于处理CPU密集型应用,Nodejs创建子进程有7个API,其中带Async的是同步API,不带的是异步API child_process.exec(command[, options][, callback]) command:要运行的命令&am…

NVR批量管理软件/平台EasyNVR多个NVR同时管理支持对接阿里云、腾讯云、天翼云、亚马逊S3云存储

随着云计算技术的日益成熟,越来越多的企业开始将其业务迁移到云端,以享受更为灵活、高效且经济的服务模式。在视频监控领域,云存储因其强大的数据处理能力和弹性扩展性,成为视频数据存储的理想选择。NVR批量管理软件/平台EasyNVR&…

2024年编程语言排行榜:技术世界的新星与常青树

随着技术的不断进步,编程语言的流行度也在不断变化。今天,就让我们一起来看看2024年的编程语言排行榜,探索哪些语言在技术世界中占据了主导地位。 1. Python:稳居榜首 Python以其在人工智能、数据科学、网络开发等多个领域的广泛…

MFC工控项目实例二十八模拟量信号每秒采集100次

采用两个多媒体定时器,一个0.1秒计时,另一个用来对模拟量信号采集每秒100次.。 1、在SEAL_PRESSUREDlg.h中添加代码 class CSEAL_PRESSUREDlg : public CDialog { public:CSEAL_PRESSUREDlg(CWnd* pParent NULL); // standard constructor.&#xff0e…

基于MoviNet检测视频中危险暴力行为

项目源码获取方式见文章末尾! 600多个深度学习项目资料,快来加入社群一起学习吧。 《------往期经典推荐------》 项目名称 1.【Faster & Mask R-CNN模型实现啤酒瓶瑕疵检测】 2.【卫星图像道路检测DeepLabV3Plus模型】 3.【GAN模型实现二次元头像生…

ArcGIS003:ArcMap常用操作0-50例动图演示

摘要:本文以动图形式介绍了ArcMap软件的基本操作,包括快捷方式创建、管理许可服务、操作界面元素(如内容列表、目录树、搜索窗口、工具箱、Python窗口、模型构建器窗口等)的打开与关闭、位置调整及合并,设置默认工作目…

NVR批量管理软件/平台EasyNVR多个NVR同时管理支持视频投放在电视墙上

在当今智能化、数字化的时代,视频监控已经成为各行各业不可或缺的一部分,无论是公共安全、交通管理、企业监控还是智慧城市建设,都离不开高效、稳定的视频监控系统的支持。而在这些应用场景中,将监控视频实时投放到大屏幕电视墙上…

asp.net core 跨域配置不起作用的原因

1、中间件配置跨域的顺序不对 中间件顺序配置对了基本上就能解决大部分问题中间件顺序配置对了基本上就能解决大部分问题 附上官网简单的启用跨域的代码 var MyAllowSpecificOrigins "_myAllowSpecificOrigins";var builder WebApplication.CreateBuilder(args);…

Linux 命令解释器-shell

概念 shell :壳,命令解释器,负责解析用户输入的命令 分类: 内置命令 (shell 内置 ) , shell 为了完成自我管理和基本的管理,不同的 shell 内置不同的命令,但是大 部分都差不多 外置命令&…

【开源免费】基于SpringBoot+Vue.JS网上超市系统(JAVA毕业设计)

本文项目编号 T 037 ,文末自助获取源码 \color{red}{T037,文末自助获取源码} T037,文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析 六、核心代码6.1 查…

apisix高性能网关实现一机一密

基于 APISIX 的一机一密实现方案 概述 基于 Apache APISIX 网关的一机一密实现方案,通过自主开发的自定义插件实现设备级别的密钥管理和加密通信。本方案通过扩展 APISIX 的插件机制,实现高可用、可扩展的 API 安全防护。 本方案的主要特点: …

嵌入式Linux的AXI平台(platform)驱动教程

本文以JFMQL100的Linux系统的AXI接口的平台驱动为例,介绍嵌入式Linux的平台驱动编写、测试软件编写以及验证方式。本文的方法适用于任意嵌入式芯片Linux的物理地址映射的平台(platform)驱动的编写、测试与应用。 本文中AXI的开始地址为0x8000…

Visual Studio Code(VSCode)中编写 TypeScript 代码

在 Visual Studio Code(VSCode)中编写 TypeScript 代码通常需要以下配置: 一、安装必要的扩展 TypeScript 插件:由微软官方提供,提供了语法高亮、错误检查、代码补全等功能。 二、配置 tsconfig.json 文件(…

视频设备一体化监控运维方案

随着平安城市、雪亮工程等项目建设的号召,视频监控系统的建设如火如荼地开展。无论在公共场所、企业单位、住宅小区、矿山工地还是交通枢纽,视频监控系统已成为保障安全、维护秩序和提升管理效率的重要工具。但由于对视频监控系统中的前端设备&#xff0…

第十八章 Vue组件样式范围配置之scoped

目录 一、引言 二、案例演示 2.1. 工程结构图 2.2. 核心代码 2.2.1. main.js 2.2.2. App.vue 2.2.3. BaseOne.vue 2.2.4. BaseTwo.vue 2.3. 运行效果 2.4. 调整代码 2.4.1. BaseTwo.vue 2.4.2. 运行效果 三、scoped原理 一、引言 前面的几个章节在介绍组件的时…

可口可乐三季报 | 数字化助力,营收超预期 | ​eBest

可口可乐公司近日公布了2024年第三季度业绩报告。报告特别强调了数字技术,尤其是AI人工智能对推动增长的重要作用。 第三季度,可口可乐公司交出了一份亮眼的成绩单,营收和每股收益均超出市场预期,显示出公司业务的强大韧性和长期…

书生大模型实战营 L0 入门岛

书生大模型训练营入门岛任务——训练营链接 1. Linux前置知识 任务:端口转发 当使用vscode远程连接服务器时,在服务器运行的任务,vscode会自动帮忙进行端口映射,方便本地进行访问。 2. Python前置知识 任务1:Leec…

配置mysql 主主模式 GTID

文章目录 一、前提二、修改my.cnf主1 10.255.131.9主2 10.255.131.10 三、配置主主3.1 配置主 10.255.131.93.2 配置从 10.255.131.103.3 配置主 10.255.131.103.4 配置从 10.255.131.9 四、验证五、同步问题排查以及恢复5.1 查看同步状态5.2 查看同步是否数据一致性&#xff0…