Java中的并发工具类CountDownLatch,CyclicBarrier,Semaphore
Java中的并发工具类CountDownLatch,CyclicBarrier,Semaphore
CountDownLatch(倒计时门闩)
核心作用:让一个或多个线程等待其他线程完成一系列操作后,再继续执行。
- 基于 “计数器” 实现:初始化时指定一个计数(需等待的事件数量)。
- 当一个线程完成任务后,调用
countDown()
方法让计数减 1。 - 等待的线程通过
await()
方法阻塞,直到计数减为 0 时被唤醒,继续执行。
特点:
- 计数一旦到 0,就不可重置(一次性使用)。
- 适用于 “一对多” 或 “多对多” 的等待场景(如主线程等待多个子线程初始化完成)。
场景示例
用户下了一个外卖订单,包含 宫保鸡丁、鱼香肉丝、麻婆豆腐 三道菜(对应 “数据源”)。
- 线程 1(厨师 A):负责做宫保鸡丁,做完后调用
countDown()
。 - 线程 2(厨师 B):负责做鱼香肉丝,做完后调用
countDown()
。 - 线程 3(厨师 C):负责做麻婆豆腐,做完后调用
countDown()
。 - 汇总线程(骑手):需要等三道菜都做好(
cdl.await()
),才能取餐配送(“对三道菜汇总打包”)。
代码生成
import java.util.concurrent.CountDownLatch;public class TakeawayDemo {public static void main(String[] args) {// 初始化计数器为3(需要等待3道菜做好)CountDownLatch mealLatch = new CountDownLatch(3);// 厨师A:做宫保鸡丁Thread chefA = new Thread(() -> {try {System.out.println("厨师A开始做宫保鸡丁...");Thread.sleep(2000); // 模拟做菜耗时System.out.println("厨师A:宫保鸡丁做好了!");} catch (InterruptedException e) {e.printStackTrace();} finally {// 菜做好,计数器减1mealLatch.countDown();}});// 厨师B:做鱼香肉丝Thread chefB = new Thread(() -> {try {System.out.println("厨师B开始做鱼香肉丝...");Thread.sleep(1500); // 模拟做菜耗时System.out.println("厨师B:鱼香肉丝做好了!");} catch (InterruptedException e) {e.printStackTrace();} finally {mealLatch.countDown();}});// 厨师C:做麻婆豆腐Thread chefC = new Thread(() -> {try {System.out.println("厨师C开始做麻婆豆腐...");Thread.sleep(1000); // 模拟做菜耗时System.out.println("厨师C:麻婆豆腐做好了!");} catch (InterruptedException e) {e.printStackTrace();} finally {mealLatch.countDown();}});// 骑手:等待所有菜做好后取餐Thread rider = new Thread(() -> {try {System.out.println("骑手已到达餐厅,等待所有菜品完成...");// 阻塞等待,直到计数器归0mealLatch.await();System.out.println("所有菜品已备齐,骑手取餐并开始配送!");} catch (InterruptedException e) {e.printStackTrace();}});// 启动所有线程chefA.start();chefB.start();chefC.start();rider.start();}
}
厨师A开始做宫保鸡丁...
厨师B开始做鱼香肉丝...
厨师C开始做麻婆豆腐...
骑手已到达餐厅,等待所有菜品完成...
厨师C:麻婆豆腐做好了!
厨师B:鱼香肉丝做好了!
厨师A:宫保鸡丁做好了!
所有菜品已备齐,骑手取餐并开始配送!
CyclicBarrier(循环屏障)
核心作用:让一组线程到达某个 “屏障点” 后互相等待,直到所有线程都到达屏障点,再一起继续执行后续操作。
- 基于 “等待计数” 实现:初始化时指定参与的线程数量( parties )。
- 每个线程到达屏障点时,调用
await()
方法阻塞,直到最后一个线程到达,所有线程被同时唤醒。 - 可重复使用:所有线程通过屏障后,计数会重置,可再次用于协调同一组线程。
特点:
- 线程之间是 “互相等待”(而非单方面等待)。
- 支持在所有线程到达屏障后,自动执行一个预先设置的
Runnable
任务(如汇总结果)。 - 适用于 “多线程分阶段协同工作” 场景(如多个线程先各自处理数据,全部完成后再一起进入下一阶段)。

场景示例
3 名玩家各自从服务器拉取并加载资源,加载完成后互相等待,全部就绪后共同进入游戏地图。
- 玩家 1~3(3 个线程):各自从本地加载地图、模型、音效等资源(耗时不同,比如玩家 1 需 10 秒,玩家 2 需 15 秒, etc.)。每个玩家加载完成后,会显示 “已就绪” 并等待其他人(调用
barrier.await()
)。 - 游戏屏障(
CyclicBarrier
):初始化时设定 “需要 3 个玩家到达”(new CyclicBarrier(3, 游戏开始动作)
)。当最后 1 名玩家加载完成并调用await()
后,屏障打开。 - 共同动作:所有玩家被同时唤醒,屏幕显示 “所有玩家加载完成,游戏开始!”,一起进入游戏地图。
代码生成
import java.util.concurrent.CyclicBarrier;public class GameLoadingDemo {public static void main(String[] args) {// 初始化 CyclicBarrier:需要3个线程到达屏障,之后执行“共同进入地图”的动作CyclicBarrier barrier = new CyclicBarrier(3, () -> {System.out.println("所有玩家加载完成,一起进入游戏地图!");});// 玩家1:加载耗时10秒Thread player1 = new Thread(() -> {try {System.out.println("玩家1 开始从服务器拉取并加载地图、模型、音效等资源...");Thread.sleep(10000); // 模拟10秒加载过程System.out.println("玩家1 加载完成,等待其他玩家...");barrier.await(); // 到达屏障,等待所有玩家就绪System.out.println("玩家1 进入游戏地图");} catch (Exception e) {e.printStackTrace();}}, "玩家1");// 玩家2:加载耗时15秒Thread player2 = new Thread(() -> {try {System.out.println("玩家2 开始从服务器拉取并加载地图、模型、音效等资源...");Thread.sleep(15000); // 模拟15秒加载过程System.out.println("玩家2 加载完成,等待其他玩家...");barrier.await(); // 到达屏障,等待所有玩家就绪System.out.println("玩家2 进入游戏地图");} catch (Exception e) {e.printStackTrace();}}, "玩家2");// 玩家3:加载耗时20秒Thread player3 = new Thread(() -> {try {System.out.println("玩家3 开始从服务器拉取并加载地图、模型、音效等资源...");Thread.sleep(20000); // 模拟20秒加载过程System.out.println("玩家3 加载完成,等待其他玩家...");barrier.await(); // 到达屏障,等待所有玩家就绪System.out.println("玩家3 进入游戏地图");} catch (Exception e) {e.printStackTrace();}}, "玩家3");// 启动3个玩家的加载线程player1.start();player2.start();player3.start();}
}
玩家1 开始从服务器拉取并加载地图、模型、音效等资源...
玩家2 开始从服务器拉取并加载地图、模型、音效等资源...
玩家3 开始从服务器拉取并加载地图、模型、音效等资源...// 10秒后
玩家1 加载完成,等待其他玩家...// 15秒后
玩家2 加载完成,等待其他玩家...// 20秒后
玩家3 加载完成,等待其他玩家...
所有玩家加载完成,一起进入游戏地图!
玩家1 进入游戏地图
玩家2 进入游戏地图
玩家3 进入游戏地图
Semaphore(信号量)
核心作用:控制同时访问某个资源(或执行某个操作)的线程数量,本质是 “许可管理”。
- 基于 “许可数” 实现:初始化时指定许可数量(允许的最大并发数)。
- 线程需要访问资源时,调用
acquire()
申请许可(若许可不足则阻塞);访问完成后,调用release()
释放许可(供其他线程使用)。
特点:
- 许可数可动态调整(通过
release(n)
增加许可,acquire(n)
一次性获取多个许可)。 - 适用于 “限流” 或 “资源池控制” 场景(如控制数据库连接数、并发请求数)。
场景示例
数据库只有 3
个连接(有限资源)Semaphore sem = new Semaphore(3)
初始化了 3
个 “许可”,表示最多同时有 3 个线程能拿到连接
- 客户端 1、2、3 调用
sem.acquire()
时,能成功拿到许可,直接访问数据库;使用完毕后调用sem.release()
释放许可。 - 客户端 4、5 调用
sem.acquire()
时,因 3 个许可已被占满,会阻塞等待,直到有客户端释放许可后,才能拿到连接。
代码表示
import java.util.concurrent.Semaphore;public class DBConnectionPool {public static void main(String[] args) {// 初始化信号量:最多允许3个线程同时获取数据库连接(3个许可)Semaphore connectionSemaphore = new Semaphore(3);// 模拟5个客户端线程尝试访问数据库for (int i = 1; i <= 5; i++) {final int clientId = i;new Thread(() -> {try {System.out.println("客户端" + clientId + " 尝试获取数据库连接...");// 申请连接(获取许可):若已达3个连接,会阻塞等待connectionSemaphore.acquire();System.out.println("客户端" + clientId + " 成功获取连接,开始执行SQL...");// 模拟数据库操作耗时(1-3秒)Thread.sleep((long) (Math.random() * 2000 + 1000));} catch (InterruptedException e) {e.printStackTrace();} finally {// 释放连接(归还许可)connectionSemaphore.release();System.out.println("客户端" + clientId + " 释放连接,当前可用连接数+1");}}).start();}}
}
客户端1 尝试获取数据库连接...
客户端2 尝试获取数据库连接...
客户端3 尝试获取数据库连接...
客户端4 尝试获取数据库连接...
客户端5 尝试获取数据库连接...
客户端1 成功获取连接,开始执行SQL...
客户端2 成功获取连接,开始执行SQL...
客户端3 成功获取连接,开始执行SQL...
客户端2 释放连接,当前可用连接数+1
客户端4 成功获取连接,开始执行SQL...
客户端1 释放连接,当前可用连接数+1
客户端5 成功获取连接,开始执行SQL...
客户端3 释放连接,当前可用连接数+1
客户端4 释放连接,当前可用连接数+1
客户端5 释放连接,当前可用连接数+1