CountDownLatch
、CyclicBarrier
和Semaphore
都是Java并发包中提供的一些同步辅助工具,它们用于控制并发任务的执行顺序和协调多个线程之间的操作。下面是它们的异同点以及使用示例:
CountDownLatch
- 作用:它允许一个或多个线程等待一组其他线程完成执行。
- 计数器:初始化一个给定的计数值,每当一个线程完成其任务时,计数器递减。当计数器达到0时,所有等待的线程都会继续执行。
- 一次性:计数器只能递减,一旦达到0,不能再重置,因此
CountDownLatch
是一次性的。
使用示例:
假设有5个线程需要执行一个任务,并且必须等待这些任务完成后,主线程才能继续执行。
CountDownLatch latch = new CountDownLatch(5);
for (int i = 0; i < 5; i++) {
new Thread(() -> {
// 执行任务
System.out.println("子线程 " + i + " 完成");
latch.countDown(); // 计数器递减
}).start();
}
try {
latch.await(); // 主线程等待,直到计数器达到0
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("所有任务完成,主线程继续执行");
CyclicBarrier
- 作用:类似于
CountDownLatch
,CyclicBarrier
也用于等待一组线程在某个点上汇合后再继续执行。但它可以被重置并且循环使用。 - 循环使用:当所有线程都到达屏障点时,计数器会重置,并且屏障可以再次使用。
使用示例:
假设有一组线程需要在每个时间点同步它们的进度,然后继续执行下一个阶段的任务。
CyclicBarrier barrier = new CyclicBarrier(5, () -> System.out.println("所有线程在屏障点汇合"));
for (int i = 0; i < 5; i++) {
new Thread(() -> {
for (int j = 0; j < 10; j++) {
// 执行任务
System.out.println("线程 " + i + " 完成第 " + j + " 阶段");
try {
barrier.await(); // 等待其他线程到达屏障点
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}
}).start();
}
Semaphore
- 作用:信号量,用于控制同时访问某个特定资源的线程数量。
- 计数器:可以初始化一个计数值,线程可以通过调用
acquire()
(获取一个许可)和release()
(释放一个许可)来控制对资源的访问。
使用示例:
假设有3个资源,但是有5个线程需要访问这些资源,每个线程最多同时只能有3个线程访问。
Semaphore semaphore = new Semaphore(3);
for (int i = 0; i < 5; i++) {
new Thread(() -> {
try {
semaphore.acquire(); // 获取一个许可
System.out.println("线程 " + i + " 获取资源");
// 模拟执行任务
Thread.sleep(1000);
System.out.println("线程 " + i + " 释放资源");
semaphore.release(); // 释放一个许可
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
异同点:
-
相同点:
- 都是同步辅助工具,用于控制线程间的协调和通信。
- 都可以用于等待多个线程完成某些操作。
-
不同点:
CountDownLatch
是一次性的,计数器只能递减;而CyclicBarrier
可以重置并循环使用。Semaphore
用于控制对资源的访问数量,而CountDownLatch
和CyclicBarrier
用于线程间的汇合点。CountDownLatch
和CyclicBarrier
通常用于线程间的等待,而Semaphore
更多用于控制对共享资源的访问。
根据具体的应用场景,可以选择合适的同步辅助工具来实现线程间的协调。