1. 基本用法
(1) CountDownLatch
用途:允许一个或多个线程等待其他线程完成操作。
核心方法:countDown()(减少计数器)、await()(阻塞直到计数器归零)。
不可重置,计数器归零后失效。
public static void CountDownLatchTest() throws InterruptedException {
// 主线程等待所有子线程完成任务
CountDownLatch latch = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {
new Thread(() -> {
// 子线程执行任务
System.out.println("子线程完成" + LocalDateTime.now());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
latch.countDown(); // 计数器减1
}).start();
}
latch.await(); // 主线程阻塞等待计数器归零
System.out.println("所有子线程已完成任务后才会执行");
}
输出:
子线程完成2025-02-13T14:17:18.676
子线程完成2025-02-13T14:17:18.676
子线程完成2025-02-13T14:17:18.676
所有子线程已完成任务后才会执行
三个线程都sleep了2秒,主线程要等到所有子线程执行完毕才执行
(2) CyclicBarrier
用途:让一组线程互相等待,直到所有线程到达某个屏障点。
可重置,通过 reset() 或自动重置。
可指定一个 Runnable 任务在所有线程到达后执行。
public static void CyclicBarrierTest() {
// 3 个线程互相等待,到达屏障后继续执行
CyclicBarrier barrier = new CyclicBarrier(3, () -> {
System.out.println("所有线程到达屏障");
});
for (int i = 0; i < 3; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName()+"线程到达屏障" + LocalDateTime.now());
try {
barrier.await(); // 等待其他线程
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (BrokenBarrierException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName()+"线程继续执行");
}).start();
}
}
输出:
Thread-2线程到达屏障2025-02-13T14:31:46.270
Thread-0线程到达屏障2025-02-13T14:31:46.270
Thread-1线程到达屏障2025-02-13T14:31:46.270
所有线程到达屏障
Thread-1线程继续执行
Thread-0线程继续执行
Thread-2线程继续执行
每个线程到达屏障之后等待,所有线程都到达屏障之后才会继续执行。
如果所有线程都到达屏障之后,Runnable 任务抛出异常会怎么样?
每个线程不会继续执行后面的代码。
如果循环次数设置从3改为6会怎么样?
CyclicBarrier 的计数器在屏障触发后会自动重置,因此可以多次使用。
6 个线程会被分成 2 组(每组 3 个线程)。
如果循环次数不是屏障数的整数倍(例如循环 5 次,屏障参数为 3),
最后一批线程会永久等待,导致程序卡死(除非处理超时或中断)。
(3) Semaphore
用途:控制同时访问某个资源的线程数量。
核心方法:acquire()(获取许可)、release()(释放许可)。
支持公平/非公平模式。
public static void SemaphoreTest() throws InterruptedException {
// 限制同时访问资源的线程数为 2
Semaphore semaphore = new Semaphore(2);
for (int i = 0; i < 5; i++) {
new Thread(() -> {
try {
semaphore.acquire(); // 获取许可
System.out.println("线程占用资源" + LocalDateTime.now());
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release(); // 释放许可
}
}).start();
}
}
输出:
线程占用资源2025-02-13T14:40:37.731
线程占用资源2025-02-13T14:40:37.731
线程占用资源2025-02-13T14:40:39.735
线程占用资源2025-02-13T14:40:39.735
线程占用资源2025-02-13T14:40:41.742
Semaphore设置为2,所以每2个线程执行完毕就不再继续执行。直到有线程 semaphore.release(); // 释放许可。观察输出的执行时间,即可得到结论。
2. 区别对比
特性 | CountDownLatch | CyclicBarrier | Semaphore |
---|---|---|---|
核心用途 | 等待其他线程完成任务 | 多个线程相互等待 | 控制并发线程数量 |
计数器重置 | 否(一次性) | 是(可循环使用) | 是(许可证可重复获取/释放) |
关键方法 | countDown(), await() | await() | acquire(), release() |
线程角色 | 主线程等待子线程 | 线程间互相等待 | 资源访问控制 |
是否支持回调任务 | 否 | 是(到达屏障后执行任务) | 否 |
异常处理 | 等待线程可能被中断 | 屏障破坏后抛出异常 | 无特殊处理 |
3. 适用场景
CountDownLatch
主线程等待所有子线程初始化完成。
并行任务完成后汇总结果。
CyclicBarrier
多线程分阶段处理数据(如 MapReduce)。
多线程协同测试(同时开始压力测试)。
Semaphore
数据库连接池限流。
控制文件读写并发数。
4. 关键总结
计数器特性:
CountDownLatch 是单向计数器(减少),不可重置。
CyclicBarrier 是循环计数器,可重复使用。
Semaphore 是许可证计数器,可动态增减。
线程协作模型:
CountDownLatch:主线程等待子线程。
CyclicBarrier:线程间互相等待。
Semaphore:限制资源访问的并发量。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。