内容概述
在现代并发编程中,线程的协调和同步至关重要。Java提供了丰富的工具来处理线程同步,其中CountDownLatch
和 CyclicBarrier
是两种常用的并发工具类。它们都存在于java.util.concurrent
包中,虽然都有线程协调的功能,但其使用场景和实现机制有所不同。
本文将深入介绍这两种工具类,详细探讨它们的使用场景、实现原理,并通过代码示例帮助您更好地理解和掌握这两种工具的应用。
学习目标
通过学习本文,您将能够:
- 了解
CountDownLatch
和CyclicBarrier
的适用场景。 - 掌握这两种工具类的基本用法和实现原理。
- 能够在实际开发中使用这些工具类来解决复杂的线程同步问题。
1. CountDownLatch:计数器倒数锁
概念
CountDownLatch
是一个用于让一个或多个线程等待其他线程完成某些操作的同步工具。它通过一个倒数计数器来实现这一功能。当计数器到达零时,所有阻塞等待的线程将被释放,继续执行。
使用场景
- 线程协作:主线程需要等待多个工作线程完成任务后再继续执行。
- 等待某些前置任务完成:例如,主线程需要等待系统初始化或数据加载完成,才能继续处理其他任务。
主要方法
await()
:使调用该方法的线程等待,直到倒数计数器减为零。countDown()
:倒数计数器减一,每当一个任务完成时调用一次。
实现原理
CountDownLatch
通过内部维护一个共享的倒数计数器,多个线程可以调用countDown()
方法减少计数。当计数器达到0时,所有因调用await()
方法而等待的线程将被释放,恢复执行。
示例代码
import java.util.concurrent.CountDownLatch;
public class CountDownLatchExample {
public static void main(String[] args) throws InterruptedException {
int threadCount = 3;
CountDownLatch latch = new CountDownLatch(threadCount);
// 创建并启动3个线程
for (int i = 0; i < threadCount; i++) {
new Thread(new Worker(latch)).start();
}
// 主线程等待所有工作线程完成
latch.await();
System.out.println("所有线程已完成,主线程继续执行");
}
static class Worker implements Runnable {
private CountDownLatch latch;
public Worker(CountDownLatch latch) {
this.latch = latch;
}
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() + " 正在执行任务...");
Thread.sleep(1000); // 模拟任务执行时间
System.out.println(Thread.currentThread().getName() + " 完成任务");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
latch.countDown(); // 任务完成后计数器减1
}
}
}
}
输出示例:
Thread-0 正在执行任务...
Thread-1 正在执行任务...
Thread-2 正在执行任务...
Thread-0 完成任务
Thread-1 完成任务
Thread-2 完成任务
所有线程已完成,主线程继续执行
2. CyclicBarrier:循环栅栏
概念
CyclicBarrier
用于让一组线程互相等待,直到所有线程都到达某个屏障点 。与CountDownLatch
不同的是,CyclicBarrier
可以多次使用,即屏障被打开后可以重置,允许线程再次进入。
使用场景
- 多线程同步:多个线程在某个任务的某个阶段需要同步,然后继续下一阶段。
- 阶段性任务:任务分阶段执行,每个阶段结束时需要等待所有线程到达,再进入下一个阶段。
主要方法
await()
:表示当前线程到达了屏障点,等待其他线程都到达后一起继续执行。
实现原理
CyclicBarrier
通过维护一个计数器记录有多少线程调用了await()
,每当一个线程到达时,计数器减一。当所有线程到达时,屏障被触发,所有等待的线程继续执行。屏障可以重复使用,即所有线程突破屏障后,它会自动重置。
示例代码
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierExample {
public static void main(String[] args) {
int threadCount = 3;
CyclicBarrier barrier = new CyclicBarrier(threadCount, new Runnable() {
@Override
public void run() {
System.out.println("所有线程已到达屏障点,执行屏障操作");
}
});
// 创建并启动3个线程
for (int i = 0; i < threadCount; i++) {
new Thread(new Worker(barrier)).start();
}
}
static class Worker implements Runnable {
private CyclicBarrier barrier;
public Worker(CyclicBarrier barrier) {
this.barrier = barrier;
}
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() + " 正在执行任务...");
Thread.sleep(1000); // 模拟任务执行时间
System.out.println(Thread.currentThread().getName() + " 到达屏障点");
barrier.await(); // 等待其他线程到达屏障点
System.out.println(Thread.currentThread().getName() + " 突破屏障继续执行");
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}
}
}
输出示例:
Thread-0 正在执行任务...
Thread-1 正在执行任务...
Thread-2 正在执行任务...
Thread-0 到达屏障点
Thread-1 到达屏障点
Thread-2 到达屏障点
所有线程已到达屏障点,执行屏障操作
Thread-0 突破屏障继续执行
Thread-1 突破屏障继续执行
Thread-2 突破屏障继续执行
3. CountDownLatch与CyclicBarrier的对比
特性 | CountDownLatch | CyclicBarrier |
---|---|---|
主要功能 | 等待其他线程完成任务,倒数计数器为零时释放 | 线程等待彼此到达同步点,继续执行 |
重用性 | 不能重用 | 可以重用 |
计数器变化方式 | 计数器只能递减,无法重置 | 计数器可以重置,每次都可以再次使用 |
适用场景 | 线程协作,等待所有线程完成 | 多线程阶段同步,线程互相等待 |
总结
CountDownLatch
和 CyclicBarrier
都是Java并发编程中常用的线程同步工具。CountDownLatch
更适合于一次性的线程协调 ,如等待多个线程完成任务;而CyclicBarrier
则适合阶段性的同步,允许线程在某些阶段定期同步后继续执行。理解和掌握这些工具类,可以帮助开发者更好地处理复杂的线程同步问题,提高多线程程序的可控性和性能。
通过本文,您应该能够选择合适的工具类来实现多线程任务的同步与协调。
学习目标达成 :你现在应该能够使用 CountDownLatch
和 CyclicBarrier
实现复杂的线程同步操作,合理地协调多线程之间的执行顺序。