引言:为什么需要并发工具?
Java并发工具是java.util.concurrent包下的一系列高级工具,用于简化多线程编程中的线程协作、资源控制和同步操作。
并发工具类比
想象一个建筑工地:
CountDownLatch(倒计时锁):
- 像施工队长说"等3个队伍都到齐了才开工"
- 3个队伍陆续到达,每到达一个就倒计时减1
- 倒计时到0时,开工
CyclicBarrier(循环栅栏):
- 像接力赛,每4个运动员到齐后一起起跑
- 第一轮结束后,可以继续用于第二轮
Semaphore(信号量):
- 像停车场的闸门,只有2个许可
- 每进去一辆车,许可减1
- 许可用完时,其他车必须等待
CountDownLatch - 倒计时锁
CountDownLatch是一个一次性的同步工具,允许一个或多个线程等待其他线程完成操作。
核心概念
- 计数器:初始化时设置一个值
- 等待:调用await()的线程会阻塞,直到计数器为0
- 倒计时:其他线程调用countDown(),计数器减1
- 一次性:计数器到0后,不能重置
工作原理图
初始状态(count = 3):
线程A ─┐
├─ await() → 阻塞
线程B ─┤
│
线程C ─┘
线程D ─ countDown() → count = 2
线程E ─ countDown() → count = 1
线程F ─ countDown() → count = 0 → 解除阻塞,A、B、C继续执行
基本用法
import java.util.concurrent.CountDownLatch;
CountDownLatch latch = new CountDownLatch(3);
// 工作线程
for (int i = 1; i <= 3; i++) {
final int taskId = i;
new Thread(() -> {
System.out.println("任务" + taskId + "开始执行...");
try { Thread.sleep(100 * taskId); } catch (InterruptedException e) {}
System.out.println("任务" + taskId + "执行完成!");
latch.countDown(); // 计数减1
}).start();
}
// 等待所有任务完成
latch.await(); // 阻塞,直到计数器为0
System.out.println("所有任务已完成!");
使用场景
启动服务:等所有依赖服务都启动完成后再启动主服务
多线程计算:等所有子任务计算完成后汇总结果
游戏加载:等所有资源加载完成后进入游戏
超时等待
// await(timeout, unit):等待指定时间
boolean success = latch.await(5, TimeUnit.SECONDS);
if (success) {
System.out.println("所有任务完成");
} else {
System.out.println("超时,部分任务可能未完成");
}
CyclicBarrier - 循环栅栏
CyclicBarrier是一个可循环使用的同步工具,让一组线程相互等待,直到所有线程都到达某个点后一起继续执行。
核心概念
- parties:参与的线程数量
- await():线程到达时调用,等待其他线程
- cyclic:计数器可以重置,可以循环使用
- barrierAction:可选的屏障解除时的回调
工作原理图
初始状态(parties = 3):
线程A ─ await() → 到达,等待
线程B ─ await() → 到达,等待
线程C ─ await() → 到达,等待 → 3个都到达,屏障解除
↓
所有线程继续执行
↓
可重置,继续下一轮
基本用法
import java.util.concurrent.CyclicBarrier;
CyclicBarrier barrier = new CyclicBarrier(3, () -> {
System.out.println("所有选手就位,发令枪响!");
});
for (int i = 1; i <= 3; i++) {
final int playerId = i;
new Thread(() -> {
System.out.println("选手" + playerId + "准备中...");
try { Thread.sleep(100 * playerId); } catch (InterruptedException e) {}
System.out.println("选手" + playerId + "就位");
barrier.await(); // 等待其他选手
System.out.println("选手" + playerId + "开始跑!");
}).start();
}
CyclicBarrier vs CountDownLatch
| 特性 | CountDownLatch | CyclicBarrier |
|---|---|---|
| 是否可重置 | 否,一次性 | 是,可循环 |
| 谁减计数 | 等待线程之外的线程 | 等待线程自己 |
| 典型用法 | 主线程等待子任务 | 线程相互等待 |
| reset() | 不支持 | 支持 |
使用场景
接力赛:每组4个运动员都到齐后开始比赛
多线程数据处理:等所有数据加载完成后统一处理
批量任务:分批处理,每批完成后汇总
Semaphore - 信号量
Semaphore用于控制同时访问资源的线程数量,常用于限流。
核心概念
- 许可(permits):初始化时设置许可数量
- acquire():获取一个许可,没有则等待
- release():释放一个许可
- 公平性:可以设置为公平或非公平
工作原理图
初始状态(permits = 2):
┌─────────────────────────────┐
│ 停车场(2个车位) │
└─────────────────────────────┘
车辆A ─ acquire() → 进入,permits = 1
车辆B ─ acquire() → 进入,permits = 0
车辆C ─ acquire() → 等待(没车位了)
车辆D ─ acquire() → 等待
车辆A ─ release() → 离开,permits = 1
车辆C ─ acquire() → 进入,permits = 0
基本用法
import java.util.concurrent.Semaphore;
Semaphore semaphore = new Semaphore(2); // 只有2个许可
for (int i = 1; i <= 4; i++) {
final int taskId = i;
new Thread(() -> {
try {
System.out.println("任务" + taskId + "等待获取许可...");
semaphore.acquire(); // 获取许可
System.out.println("任务" + taskId + "开始执行!");
Thread.sleep(500); // 模拟执行
System.out.println("任务" + taskId + "执行完成,释放许可");
semaphore.release(); // 释放许可
} catch (InterruptedException e) {}
}).start();
}
使用场景
连接池限流:限制同时使用的连接数
API限流:限制每秒请求数
资源访问:限制同时访问文件的线程数
设备控制:限制同时使用的打印机数量
公平 vs 非公平
// 非公平(默认):可能导致饥饿
Semaphore unfair = new Semaphore(2);
// 公平:先到先得(FIFO顺序)
Semaphore fair = new Semaphore(2, true);
// 公平semaphore的acquire会等前面排队的线程
获取和释放多个许可
Semaphore semaphore = new Semaphore(3);
// 获取多个许可
semaphore.acquire(2); // 需要2个许可
// 释放多个许可
semaphore.release(2); // 释放2个许可
Exchanger - 交换器
Exchanger用于两个线程之间交换数据。
基本用法
import java.util.concurrent.Exchanger;
Exchanger exchanger = new Exchanger<>();
new Thread(() -> {
try {
String data = "数据A";
System.out.println("线程A准备交换: " + data);
String received = exchanger.exchange(data);
System.out.println("线程A收到: " + received);
} catch (InterruptedException e) {}
}).start();
new Thread(() -> {
try {
String data = "数据B";
System.out.println("线程B准备交换: " + data);
String received = exchanger.exchange(data);
System.out.println("线程B收到: " + received);
} catch (InterruptedException e) {}
}).start();
使用场景
遗传算法:交换两个染色体的数据
生产者-消费者:交换缓冲区
Phaser - 多阶段同步
Phaser是Java 7引入的更灵活的同步工具,支持多阶段任务的协调。
基本用法
import java.util.concurrent.Phaser;
Phaser phaser = new Phaser(3); // 3个参与方
for (int i = 1; i <= 3; i++) {
final int taskId = i;
new Thread(() -> {
System.out.println("任务" + taskId + "阶段1");
phaser.arriveAndAwaitAdvance(); // 等待其他任务
System.out.println("任务" + taskId + "阶段2");
phaser.arriveAndAwaitAdvance(); // 等待其他任务
System.out.println("任务" + taskId + "完成");
phaser.arriveAndDeregister(); // 完成任务
}).start();
}
对比总结
| 工具 | 用途 | 是否可重置 | 典型场景 |
|---|---|---|---|
| CountDownLatch | 倒计时等待 | 否 | 等子任务完成 |
| CyclicBarrier | 线程集合点 | 是 | 相互等待 |
| Semaphore | 限流 | 手动控制 | 连接池、API限流 |
| Exchanger | 数据交换 | N/A | 两线程交换数据 |
| Phaser | 多阶段同步 | 是 | 多阶段任务 |
实战:模拟考试系统
import java.util.concurrent.*;
public class ExamSimulation {
public static void main(String[] args) throws Exception {
System.out.println("===== 学生考试场景 =====");
int studentCount = 3;
CountDownLatch startLatch = new CountDownLatch(1); // 等发卷
CountDownLatch submitLatch = new CountDownLatch(studentCount); // 等交卷
for (int i = 1; i <= studentCount; i++) {
final int studentId = i;
new Thread(() -> {
try {
System.out.println("学生" + studentId + "等待发卷...");
startLatch.await(); // 等老师发卷
System.out.println("学生" + studentId + "开始答题...");
Thread.sleep((long)(Math.random() * 1000)); // 答题时间不同
System.out.println("学生" + studentId + "交卷!");
submitLatch.countDown();
} catch (InterruptedException e) {}
}).start();
}
Thread.sleep(500); // 准备时间
System.out.println("老师发卷!");
startLatch.countDown(); // 发出试卷
submitLatch.await(); // 等待所有学生交卷
System.out.println("所有学生交卷,老师收卷!");
}
}
实战:数据库连接池
import java.util.concurrent.*;
public class ConnectionPoolSimulation {
public static void main(String[] args) {
System.out.println("===== 数据库连接池场景 =====");
int poolSize = 2; // 连接池大小
Semaphore semaphore = new Semaphore(poolSize);
for (int i = 1; i <= 4; i++) {
final int taskId = i;
new Thread(() -> {
try {
System.out.println("任务" + taskId + "等待获取连接...");
semaphore.acquire();
System.out.println("任务" + taskId + "获取到连接,开始执行SQL...");
Thread.sleep(1000); // 模拟SQL执行
System.out.println("任务" + taskId + "执行完成,释放连接");
semaphore.release();
} catch (InterruptedException e) {}
}).start();
}
}
}
小结
- CountDownLatch:倒计时锁,一次性,用于主线程等待子任务
- CyclicBarrier:循环栅栏,可重置,用于线程相互等待
- Semaphore:信号量,用于限流,控制同时访问资源的线程数
- Exchanger:交换器,用于两个线程交换数据
- Phaser:多阶段同步器,用于多阶段任务协调
- 选择工具时考虑:是否需要重置、线程如何协作