在多线程编程的世界里,线程间的同步与协作是一个绕不开的话题。而CountdownLatch作为 Java 并发包中一个重要的同步工具类,能够轻松实现多个线程之间的协调工作,让我们的程序更加高效、有序地运行。今天,我们就来深入探讨CountdownLatch的方方面面,看看它是如何在多线程场景中发挥作用的。
一、CountdownLatch 是什么
CountdownLatch是 Java.util.concurrent 包下的一个类,它允许一个或多个线程等待其他线程完成一系列操作后再继续执行。从名字上看,"Countdown" 意为倒计时,"Latch" 意为门闩,这形象地描述了它的工作机制:就像一道门,只有当倒计时结束(计数器归 0)时,这道门才会打开,等待的线程才能通过。
比如在实际开发中,我们可能会遇到这样的场景:主线程需要等待多个子线程完成初始化工作后,才能开始执行后续的任务。这时,CountdownLatch就可以派上用场了,它能让主线程精准地等待所有子线程完成初始化。
二、CountdownLatch 的核心原理
CountdownLatch的核心在于一个计数器。当我们创建CountdownLatch对象时,会指定一个初始的计数值。这个计数值代表着需要等待的事件数量。
- 当一个线程完成了相应的操作后,就会调用countDown()方法,此时计数器的值会减 1。
- 而需要等待的线程则会调用await()方法,该方法会让线程进入阻塞状态,直到计数器的值变为 0,线程才会被唤醒,继续执行后续操作。
值得注意的是,计数器的值一旦变为 0,就无法再被重置。这意味着CountdownLatch是一次性的,一旦使用完毕,就不能再次使用了。
三、CountdownLatch 的常用方法
1. 构造方法
CountdownLatch只有一个构造方法:
java
public CountDownLatch(int count)
其中,count就是初始的计数值,它必须是一个非负整数。如果count为 0,那么调用await()方法的线程会立即返回。
2. await () 方法
java
public void await() throws InterruptedException
该方法会使当前线程进入阻塞状态,直到计数器的值变为 0。如果在阻塞过程中线程被中断,会抛出InterruptedException异常。
此外,还有一个带超时时间的重载方法:
java
public boolean await(long timeout, TimeUnit unit) throws InterruptedException
如果在指定的超时时间内计数器的值变为 0,返回true;否则,返回false。
3. countDown () 方法
csharp
public void countDown()
该方法会将计数器的值减 1。如果减 1 后计数器的值变为 0,会唤醒所有因调用await()方法而阻塞的线程。
四、CountdownLatch 的使用场景案例
1. 多线程任务执行后汇总结果
假设我们需要从多个数据源获取数据,然后对这些数据进行汇总处理。我们可以让每个线程负责从一个数据源获取数据,主线程等待所有线程获取完成后再进行汇总。
java
import java.util.concurrent.CountDownLatch;
public class DataSummarizer {
public static void main(String[] args) throws InterruptedException {
int dataSourceCount = 3;
CountDownLatch latch = new CountDownLatch(dataSourceCount);
for (int i = 0; i < dataSourceCount; i++) {
final int sourceId = i;
new Thread(() -> {
System.out.println("线程" + Thread.currentThread().getId() + "开始从数据源" + sourceId + "获取数据");
// 模拟获取数据的过程
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程" + Thread.currentThread().getId() + "完成从数据源" + sourceId + "获取数据");
latch.countDown();
}).start();
}
System.out.println("主线程等待所有数据源获取数据完成...");
latch.await();
System.out.println("所有数据源数据获取完成,主线程开始汇总数据");
// 汇总数据的操作
}
}
在这个案例中,我们创建了一个计数值为 3 的CountdownLatch,因为有 3 个数据源需要获取数据。每个线程获取完数据后都会调用countDown()方法,当 3 个线程都执行完该方法后,计数器变为 0,主线程从await()方法返回,开始汇总数据。
2. 模拟并发测试
在进行并发测试时,我们可能需要让所有测试线程同时开始执行任务,以模拟真实的并发场景。这时,可以使用CountdownLatch来实现。
csharp
import java.util.concurrent.CountDownLatch;
public class ConcurrentTester {
public static void main(String[] args) throws InterruptedException {
int threadCount = 5;
CountDownLatch startLatch = new CountDownLatch(1);
CountDownLatch endLatch = new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; i++) {
new Thread(() -> {
try {
startLatch.await(); // 等待开始信号
System.out.println("线程" + Thread.currentThread().getId() + "开始执行任务");
// 模拟任务执行
Thread.sleep(1000);
System.out.println("线程" + Thread.currentThread().getId() + "完成任务");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
endLatch.countDown();
}
}).start();
}
System.out.println("准备就绪,所有线程即将开始执行任务...");
Thread.sleep(2000); // 等待所有线程准备好
startLatch.countDown(); // 发出开始信号
endLatch.await(); // 等待所有线程完成任务
System.out.println("所有线程任务执行完毕");
}
}
这里我们使用了两个CountdownLatch,startLatch用于控制所有线程同时开始执行任务,其计数值为 1,当主线程调用countDown()方法后,所有等待的线程同时开始执行;endLatch用于等待所有线程完成任务,计数值为线程数量,当所有线程执行完任务后,主线程才会继续执行。
五、CountdownLatch 的优缺点
优点
- 简单易用:CountdownLatch的 API 设计简洁明了,使用起来非常方便,容易理解和上手。
- 高效性:它基于 AQS(AbstractQueuedSynchronizer)实现,内部采用了高效的同步机制,能够在多线程环境下高效地工作。
- 灵活性:可以适用于多种多线程同步场景,如等待多个任务完成、控制线程同时开始等。
缺点
- 一次性:如前面所说,CountdownLatch的计数器一旦归 0,就无法再被重置,不能重复使用。如果需要重复使用类似的功能,就需要重新创建对象。
- 不能反向操作:只能对计数器进行减 1 操作,不能进行加 1 操作,这在某些场景下可能会带来不便。
六、CountdownLatch 与其他同步工具的对比
1. 与 CyclicBarrier 对比
- 重用性:CyclicBarrier的计数器可以通过reset()方法重置,能够重复使用;而CountdownLatch不能重置,是一次性的。
- 等待对象:CyclicBarrier是让一组线程互相等待,直到所有线程都到达某个屏障点;CountdownLatch是让一组线程等待其他线程完成操作。
- 侧重点:CyclicBarrier更侧重于多个线程之间的协作,共同完成一个任务;CountdownLatch更侧重于一个或多个线程等待其他线程的操作结果。
2. 与 Join 方法对比
- Thread类的join()方法也可以让一个线程等待另一个线程完成,但join()方法只能等待特定的线程完成,而CountdownLatch可以等待多个线程完成。
- CountdownLatch更加灵活,它可以在任何地方调用countDown()方法,不一定是在被等待的线程结束时,而join()方法只能等待线程执行完毕。
七、总结
CountdownLatch是 Java 多线程编程中一个非常实用的同步工具类,它通过一个计数器实现了线程间的同步与协作。无论是等待多个任务完成后再进行汇总,还是控制多个线程同时开始执行,CountdownLatch都能很好地满足需求。
虽然它存在一次性和不能反向操作的缺点,但在合适的场景下,它的优点足以让它成为我们处理多线程同步问题的得力助手。希望通过本文的介绍,大家能够对CountdownLatch有更深入的理解,并在实际开发中灵活运用它。