CountDownLatch
、CyclicBarrier
和Semaphore
是Java并发编程中常用的三个同步辅助类,都是基于AbstractQueuedSynchronizer
(AQS)实现,它们都可以用来协调多个线程的执行,但它们的设计目的和用法各有不同。
CountDownLatch
工作原理
CountDownLatch
使用一个整数来表示计数。当计数大于零时,调用await
方法的线程会被阻塞;当计数为零时,所有等待的线程会被唤醒。
使用方法
- 初始化计数:
CountDownLatch latch = new CountDownLatch(N);
,其中N
是线程需要等待的事件数量。 - 等待计数归零:在一个或多个线程中调用
latch.await();
,这些线程会阻塞直到计数为零。 - 递减计数:当某个事件发生时,调用
latch.countDown();
来减少计数。 - 计数归零后,所有等待的线程会被唤醒并继续执行。
典型场景
- 主线程等待子线程完成初始化工作。
- 实现并发测试中的开始信号和结束信号。
一个线程等多个线程执行完再执行
java
import java.util.concurrent.CountDownLatch;
public class Main {
public static void main(String[] args) throws InterruptedException {
int threadCount = 3; // 需要等待的线程数量
CountDownLatch latch = new CountDownLatch(threadCount);
// 启动多个线程
for (int i = 0; i < threadCount; i++) {
new Thread(() -> {
System.out.println("线程 " + Thread.currentThread().getId() + " 开始执行");
// 模拟线程工作
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程 " + Thread.currentThread().getId() + " 执行完毕");
latch.countDown(); // 执行完毕,计数减一
}).start();
}
// 等待所有线程执行完毕
latch.await();
// 所有线程执行完毕后,主线程继续执行
System.out.println("所有线程执行完毕,主线程继续执行");
}
}
CyclicBarrier
工作原理
CyclicBarrier
允许多个线程在某个点同步。每个线程到达屏障点后,会调用await
方法,这个方法会将线程阻塞,直到所有线程都到达屏障点。
使用方法
- 初始化屏障:
CyclicBarrier barrier = new CyclicBarrier(N);
,其中N
是参与线程的数量。 - 等待其他线程:每个线程执行到屏障点时调用
barrier.await();
,线程会阻塞直到所有线程都到达屏障点。 - 当所有线程都到达屏障点后,可以选择执行一个预先定义的任务(通过
CyclicBarrier
构造函数中的Runnable
参数指定)。 - 屏障被重置,可以再次使用。
典型场景
- 分阶段任务并行计算,每个阶段结束后所有线程同步,然后开始下一个阶段。
- 模拟并发环境,例如多个玩家在游戏中等待所有玩家准备就绪后开始游戏。
多个线程在某个点上相互等待
java
import java.util.concurrent.CyclicBarrier;
public class Main {
public static void main(String[] args) {
int threadCount = 3; // 需要同步的线程数量
CyclicBarrier barrier = new CyclicBarrier(threadCount);
for (int i = 0; i < threadCount; i++) {
new Thread(() -> {
System.out.println("线程 " + Thread.currentThread().getId() + " 开始执行");
// 模拟线程工作
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
barrier.await(); // 在这里等待其他线程
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("线程 " + Thread.currentThread().getId() + " 继续执行");
}).start();
}
}
}
Semaphore
工作原理
Semaphore
内部维护了一个许可集。每个线程要访问资源前必须通过acquire
方法获取一个许可,如果许可可用,则线程可以继续执行;如果没有许可,则线程会被阻塞直到有许可可用。当线程完成资源使用后,通过release
方法释放许可。
使用方法
- 初始化信号量:
Semaphore semaphore = new Semaphore(N);
,其中N
是许可的数量。 - 获取许可:线程在访问资源前调用
semaphore.acquire();
,如果许可可用,线程继续执行;否则线程阻塞。 - 释放许可:线程完成资源使用后调用
semaphore.release();
,释放许可供其他线程使用。
典型场景
- 限制对共享资源的并发访问数量,例如限制数据库连接池的大小。
- 实现生产者-消费者模式,控制生产者和消费者的并发数量。
总结
CountDownLatch
适用于一次性的事件等待,CyclicBarrier
适用于可重用的线程同步点,而Semaphore
则用于控制资源并发访问的数量。