当前位置: 首页 > news >正文

Java并发工具类详解:Semaphore、CyclicBarrier与CountDownLatch

Java并发工具类详解:Semaphore、CyclicBarrier与CountDownLatch

在多线程编程中,线程间的同步与协作是核心难题。Java并发包(java.util.concurrent)提供了多种工具类,简化了复杂场景下的线程协调逻辑。本文将深入解析三个常用工具类——SemaphoreCyclicBarrierCountDownLatch,通过场景案例、代码实现和对比分析,理清它们的适用场景与核心区别。

一、Semaphore:控制并发访问的"信号灯"

1. 核心功能

Semaphore(信号量)用于限制同时访问某个资源的线程数量,类似"限流"机制。它维护一组"许可"(permits),线程需先获取许可才能访问资源,访问完成后释放许可供其他线程使用。

2. 适用场景

  • 控制并发请求数(如接口限流)
  • 管理资源池访问(如数据库连接池、线程池)
  • 实现生产者-消费者模型中的缓冲区边界控制

3. 代码案例:数据库连接池限流

假设数据库最多支持3个并发连接,超过则需等待:

import java.util.concurrent.Semaphore;public class SemaphoreDemo {public static void main(String[] args) {// 初始化3个许可(最多3个线程同时访问)Semaphore semaphore = new Semaphore(3);System.out.println("=== 数据库连接池初始化完成,最大并发连接数:3 ===");// 模拟10个线程竞争访问数据库for (int i = 0; i < 10; i++) {final int threadId = i + 1;new Thread(() -> {try {System.out.println("线程" + threadId + "尝试获取数据库连接...");// 获取许可(若满则阻塞等待)semaphore.acquire();System.out.println("线程" + threadId + "✅ 获取到数据库连接,开始执行SQL操作...");Thread.sleep(1000); // 模拟数据库操作耗时System.out.println("线程" + threadId + "📝 SQL操作执行完成");} catch (InterruptedException e) {e.printStackTrace();} finally {// 释放许可(必须在finally中执行,避免资源泄漏)semaphore.release();System.out.println("线程" + threadId + "🔄 释放数据库连接,当前可用连接数:" + semaphore.availablePermits());}}).start();// 错开线程启动时间,便于观察输出Thread.sleep(200);}}
}

输出示例

=== 数据库连接池初始化完成,最大并发连接数:3 ===
线程1尝试获取数据库连接...
线程1✅ 获取到数据库连接,开始执行SQL操作...
线程2尝试获取数据库连接...
线程2✅ 获取到数据库连接,开始执行SQL操作...
线程3尝试获取数据库连接...
线程3✅ 获取到数据库连接,开始执行SQL操作...
线程4尝试获取数据库连接...
线程1📝 SQL操作执行完成
线程1🔄 释放数据库连接,当前可用连接数:1
线程4✅ 获取到数据库连接,开始执行SQL操作...
线程5尝试获取数据库连接...
线程2📝 SQL操作执行完成
线程2🔄 释放数据库连接,当前可用连接数:1
线程5✅ 获取到数据库连接,开始执行SQL操作...
...(后续线程按同样逻辑执行)

二、CyclicBarrier:多线程协同的"循环屏障"

1. 核心功能

CyclicBarrier(循环屏障)让多个线程相互等待,直到所有线程都到达某个"屏障点"后,再一起继续执行。与其他工具类不同,它支持重复使用(通过reset()重置屏障)。

2. 适用场景

  • 多阶段任务(如"需求分析→编码→测试"的开发流程)
  • 数据分片处理(各线程处理完分片后,等待汇总结果)
  • 模拟并发场景(所有线程就绪后同时执行)

3. 代码案例:多阶段开发流程

3个开发者需依次完成三个阶段任务,每个阶段必须全员完成后才能进入下一阶段:

import java.util.concurrent.CyclicBarrier;public class CyclicBarrierDemo {public static void main(String[] args) {int developerCount = 3; // 3个开发者线程System.out.println("=== 项目启动,共" + developerCount + "名开发者参与 ===");// 屏障触发时执行的任务(可选,如阶段总结)CyclicBarrier barrier = new CyclicBarrier(developerCount, () -> {System.out.println("======================================");System.out.println("📢 所有开发者已完成当前阶段,准备进入下一阶段!");System.out.println("======================================");});for (int i = 0; i < developerCount; i++) {final int developerId = i + 1;new Thread(() -> {try {// 第一阶段:需求分析System.out.println("开发者" + developerId + ":开始需求分析(阶段1)");Thread.sleep((long) (Math.random() * 1000) + 500); // 模拟不同耗时System.out.println("开发者" + developerId + ":需求分析完成(阶段1),等待其他人...");barrier.await(); // 等待其他开发者// 第二阶段:编码System.out.println("开发者" + developerId + ":开始编码开发(阶段2)");Thread.sleep((long) (Math.random() * 1000) + 500);System.out.println("开发者" + developerId + ":编码开发完成(阶段2),等待其他人...");barrier.await();// 第三阶段:测试System.out.println("开发者" + developerId + ":开始功能测试(阶段3)");Thread.sleep((long) (Math.random() * 1000) + 500);System.out.println("开发者" + developerId + ":功能测试完成(阶段3),等待其他人...");barrier.await();System.out.println("开发者" + developerId + ":🎉 所有阶段任务完成!");} catch (Exception e) {e.printStackTrace();}}).start();}}
}

输出示例

=== 项目启动,共3名开发者参与 ===
开发者1:开始需求分析(阶段1)
开发者2:开始需求分析(阶段1)
开发者3:开始需求分析(阶段1)
开发者2:需求分析完成(阶段1),等待其他人...
开发者1:需求分析完成(阶段1),等待其他人...
开发者3:需求分析完成(阶段1),等待其他人...
======================================
📢 所有开发者已完成当前阶段,准备进入下一阶段!
======================================
开发者1:开始编码开发(阶段2)
开发者2:开始编码开发(阶段2)
开发者3:开始编码开发(阶段2)
开发者3:编码开发完成(阶段2),等待其他人...
开发者1:编码开发完成(阶段2),等待其他人...
开发者2:编码开发完成(阶段2),等待其他人...
======================================
📢 所有开发者已完成当前阶段,准备进入下一阶段!
======================================
...(第三阶段流程类似)

三、CountDownLatch:等待前置任务的"倒计时器"

1. 核心功能

CountDownLatch(倒计时器)让一个或多个线程等待其他线程完成指定操作后再执行。它维护一个计数器,线程完成任务后调用countDown()递减计数器,当计数器为0时,所有等待的线程被唤醒。计数器不可重置,为一次性工具

2. 适用场景

  • 主线程等待子线程初始化完成(如服务器启动前的准备工作)
  • 汇总多线程任务结果(如多个线程计算部分结果,主线程汇总)
  • 模拟并发测试(等待所有线程就绪后同时执行)

3. 代码案例:服务器启动前的初始化

服务器启动前,需等待5个初始化任务(如加载配置、连接数据库)完成:

import java.util.concurrent.CountDownLatch;public class CountDownLatchDemo {public static void main(String[] args) throws InterruptedException {int taskCount = 5; // 5个初始化任务CountDownLatch latch = new CountDownLatch(taskCount);System.out.println("=== 服务器启动流程开始,需完成" + taskCount + "项初始化任务 ===");// 启动初始化任务for (int i = 0; i < taskCount; i++) {final int taskId = i + 1;new Thread(() -> {try {System.out.println("初始化任务" + taskId + ":启动(当前剩余任务数:" + latch.getCount() + ")");// 模拟不同任务耗时(1-2秒)Thread.sleep((long) (Math.random() * 1000) + 1000);System.out.println("初始化任务" + taskId + ":执行成功");} catch (InterruptedException e) {e.printStackTrace();} finally {latch.countDown(); // 任务完成,计数器-1System.out.println("初始化任务" + taskId + ":已标记完成(剩余任务数:" + latch.getCount() + ")");}}).start();}// 主线程等待所有初始化完成System.out.println("=== 主线程等待所有初始化任务完成... ===");latch.await(); // 阻塞直到计数器为0System.out.println("=== 所有初始化任务完成,服务器启动成功!🎉 ===");}
}

输出示例

=== 服务器启动流程开始,需完成5项初始化任务 ===
初始化任务1:启动(当前剩余任务数:5)
初始化任务2:启动(当前剩余任务数:5)
初始化任务3:启动(当前剩余任务数:5)
初始化任务4:启动(当前剩余任务数:5)
初始化任务5:启动(当前剩余任务数:5)
=== 主线程等待所有初始化任务完成... ===
初始化任务3:执行成功
初始化任务3:已标记完成(剩余任务数:4)
初始化任务1:执行成功
初始化任务1:已标记完成(剩余任务数:3)
初始化任务5:执行成功
初始化任务5:已标记完成(剩余任务数:2)
初始化任务2:执行成功
初始化任务2:已标记完成(剩余任务数:1)
初始化任务4:执行成功
初始化任务4:已标记完成(剩余任务数:0)
=== 所有初始化任务完成,服务器启动成功!🎉 ===

四、三者核心区别对比

特性SemaphoreCyclicBarrierCountDownLatch
核心功能控制并发访问的线程数量(限流)多线程相互等待,一起继续执行等待其他线程完成后再执行
计数器/许可许可数量(可动态调整)参与线程数(可通过reset()重置)等待的任务数(一次性递减)
阻塞对象申请许可的线程(争夺资源的线程)所有参与的线程(相互等待)调用await()的线程(等待者)
复用性可重复使用(许可释放后可再次获取)可重复使用(reset()重置屏障)一次性(计数器到0后无法重置)
典型场景限流(连接池、并发请求控制)多阶段协同任务(分步骤流程)等待前置任务完成(初始化、汇总)
核心方法acquire()/release()await()/reset()countDown()/await()

五、总结

  • Semaphore 是"门卫",通过许可机制控制同时访问资源的线程数,适合限流场景(如连接池、接口并发控制)。
  • CyclicBarrier 是"集合点",让多个线程到达屏障后一起执行下一阶段,适合多步骤协同任务(如分阶段开发流程)。
  • CountDownLatch 是"发令枪",等待所有前置任务完成后触发后续操作,适合初始化、结果汇总等场景。

这三个工具类均基于AQS(AbstractQueuedSynchronizer)实现,但设计目标不同。实际开发中需根据具体同步需求选择:如需控制并发量用Semaphore,如需多线程协同用CyclicBarrier,如需等待前置任务用CountDownLatch

http://www.dtcms.com/a/494390.html

相关文章:

  • 御剑问情_附带自动假人版_大型3D仙侠类剧情闯关手游_Linux服务端_通用视频架设教程_GM授权网页后台_运营网页后台_安卓苹果IOS双端
  • 基于信息保留与细粒度特征聚合的无人机目标检测
  • AINode部署全指南:从独立部署到Kubernetes集群部署
  • PYcharm——获取天气
  • Kafka多网卡环境配置
  • TypeScript 与淘宝 API:构建类型安全的商品数据查询前端 / Node.js 服务
  • 网站备案名称要求郴州网站排名优化
  • 百度做一个网站多少钱sns营销
  • List<map<String,Object>下划线转驼峰
  • List.subList() 返回值为什么不能强转成 ArrayList
  • phpcms网站转移网站关键词百度排名在下降
  • mac使用本地jdk启动elasticsearch解决elasticsearch启动时jdk损坏问题
  • 手机在初次联网的底层流程-关于EPC信令附着
  • 2025年红米手机上市了哪些款式,本别包含哪些版本,就上市时间、硬件参数、性能、价格等方面进行对比,加入横向竞品对比分析,按价位段划分推荐人群。
  • Go Web 编程快速入门 02 - 认识 net/http 与 Handler 接口
  • 成都网站建设网站制作济南网站制作哪家强
  • 广州做网站的网络公司网站建设美文
  • 云原生时代的数据库字段加密:在微服务与 Kubernetes 中实现合规与敏捷的统一
  • 虚拟机监控全攻略:从基础到云原生实战
  • fastgpt 社区版探究:mongo db 全文检索算法探秘
  • 防爆手机与普通手机有什么区别?防爆手机哪个牌子好?
  • 聊聊 Unity(小白专享、C# 小程序 之 日历、小闹钟)
  • 在vscode中全选后,同时在每行行尾,开始多行编辑(Mac版)
  • C4D域的重要修改层之延迟衰减和量化之解析
  • 建设银行网站网址是什么柳州电商网站建设
  • 记录WinFrom 使用 Autoupdater.NET.Official 进行软件升级更新,避免遗忘
  • 【汇编】RAX,eax,ax,ah,al 关系
  • 苍穹外卖 Day12 实战总结:Apache POI 实现 Excel 报表导出全流程解析
  • 网站分页符怎么做珠海网站建设哪个好薇
  • Redis的Docker安装