CountDownLatch
是 Java 并发包 java.util.concurrent
中的一个非常有用的工具类。它能够让一个或多个线程等待一组操作完成后再继续执行。下面我们将详细讲解 CountDownLatch
的原理及其在实际场景中的应用。
CountDownLatch 的工作原理
CountDownLatch
的核心思想是设置一个计数器,这个计数器的初始值是线程的数量。每当一个线程完成任务后,计数器就减 1。当计数器减到 0 时,所有等待的线程就会被唤醒,继续执行。
源码如下:
java
public class CountDownLatch {
private final Sync sync;
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public void countDown() {
sync.releaseShared(1);
}
private static final class Sync extends AbstractQueuedSynchronizer {
Sync(int count) {
setState(count);
}
int getCount() {
return getState();
}
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
protected boolean tryReleaseShared(int releases) {
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c - 1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
}
在上面这段代码中,CountDownLatch
使用了一个内部类 Sync
继承自 AbstractQueuedSynchronizer
,通过对 state
进行操作来控制线程的等待与唤醒。
await
方法会调用acquireSharedInterruptibly
方法,这个方法会检查state
是否为 0,如果不是 0,则线程进入等待状态。countDown
方法会调用releaseShared
方法,该方法会对state
进行减 1 操作,当state
减到 0 时,会唤醒所有等待的线程。
CountDownLatch 的使用场景
下面我们通过一个具体的例子来展示 CountDownLatch
的实际应用场景:
场景:多线程读取多个文件并处理,然后等待所有文件处理完成后,进行结果统计
java
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CountDownLatchExample {
// 处理文件的数量
private static final int THREAD_COUNT = 6;
public static void main(String[] args) throws InterruptedException {
// 创建一个具有固定线程数量的线程池对象
ExecutorService threadPool = Executors.newFixedThreadPool(THREAD_COUNT);
// 创建一个 CountDownLatch 对象,计数器的值为线程数量
final CountDownLatch countDownLatch = new CountDownLatch(THREAD_COUNT);
for (int i = 0; i < THREAD_COUNT; i++) {
final int threadNum = i;
threadPool.execute(() -> {
try {
// 模拟文件处理的业务操作
System.out.println("Thread " + threadNum + " is processing file.");
Thread.sleep(1000); // 模拟耗时操作
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 每个线程完成任务后,计数器减 1
countDownLatch.countDown();
}
});
}
// 主线程等待所有文件处理完成
countDownLatch.await();
threadPool.shutdown();
System.out.println("All files processed.");
}
}
在这个例子中,我们创建了 6 个线程来处理文件。每个线程在处理完文件后会调用 countDownLatch.countDown()
方法,使计数器减 1。主线程调用 countDownLatch.await()
方法进行等待,直到计数器减到 0,表示所有文件处理完毕,主线程才会继续执行。
使用 CompletableFuture 改进
在 Java 8 中,引入了 CompletableFuture
类,它提供了更加灵活和强大的异步编程能力。我们可以用 CompletableFuture
来改进上面的例子:
java
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class CompletableFutureExample {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 模拟 6 个文件读取任务
List<CompletableFuture<Void>> futures = IntStream.range(0, 6)
.mapToObj(i -> CompletableFuture.runAsync(() -> {
// 模拟文件处理的业务操作
System.out.println("Thread " + i + " is processing file.");
try {
Thread.sleep(1000); // 模拟耗时操作
} catch (InterruptedException e) {
e.printStackTrace();
}
}))
.collect(Collectors.toList());
// 等待所有任务完成
CompletableFuture<Void> allOf = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
allOf.get(); // 阻塞等待所有任务完成
System.out.println("All files processed.");
}
}
在这个例子中,我们使用 CompletableFuture.runAsync
方法来异步执行文件处理任务。CompletableFuture.allOf
方法用于等待所有任务完成。这样代码更加简洁,并且可以更容易地处理异步任务。
CountDownLatch 产生的意义
CountDownLatch
是 Java 并发包中的一个重要工具,它的产生解决了多线程编程中的一些常见问题,为开发者提供了一个简洁而有效的方式来协调多个线程的执行。以下是 CountDownLatch
产生的几个重要意义:
1. 简化线程同步
在多线程编程中,线程间的同步是一个非常常见的需求。传统的同步机制例如 wait
和 notify
,使用起来相对复杂且容易出错。CountDownLatch
提供了一个更高层次的抽象,使得线程同步变得更加简单和直观。
java
public class SimpleCountDownLatchExample {
private static final int THREAD_COUNT = 3;
private static CountDownLatch latch = new CountDownLatch(THREAD_COUNT);
public static void main(String[] args) {
for (int i = 0; i < THREAD_COUNT; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " is working.");
latch.countDown(); // 每个线程执行完毕后,计数器减 1
}).start();
}
try {
latch.await(); // 等待所有线程执行完毕
System.out.println("All threads have finished.");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
在这个例子中,CountDownLatch
使得主线程能够等待所有工作线程执行完毕后再继续执行,而不需要使用复杂的 wait
和 notify
机制,代码简洁易懂。
2. 控制并发任务的执行顺序
在一些场景中,我们需要确保某些任务在其他任务之前或之后执行。例如,在一个大型项目中,某些初始化操作必须在所有模块启动之前完成。CountDownLatch
可以很好地解决这个问题。
java
public class TaskOrderExample {
private static final CountDownLatch latch = new CountDownLatch(1);
public static void main(String[] args) {
new Thread(() -> {
System.out.println("Initialization...");
// 模拟初始化任务
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Initialization completed.");
latch.countDown(); // 初始化完成,计数器减 1
}).start();
new Thread(() -> {
try {
latch.await(); // 等待初始化完成
System.out.println("Starting main task...");
// 执行主任务
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
在这个例子中,主任务线程会等待初始化任务完成后再开始执行,确保了任务的执行顺序。
3. 实现一次性事件
CountDownLatch
可以用于实现一次性的事件,例如在并发测试中,我们可能需要多个线程同时开始执行。CountDownLatch
可以用来协调这些线程,使它们在同一时间点开始执行。
java
public class ConcurrentStartExample {
private static final int THREAD_COUNT = 3;
private static CountDownLatch startSignal = new CountDownLatch(1);
private static CountDownLatch doneSignal = new CountDownLatch(THREAD_COUNT);
public static void main(String[] args) {
for (int i = 0; i < THREAD_COUNT; i++) {
new Thread(() -> {
try {
startSignal.await(); // 等待开始信号
System.out.println(Thread.currentThread().getName() + " started.");
// 模拟任务执行
Thread.sleep(1000);
doneSignal.countDown(); // 任务完成,计数器减 1
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
System.out.println("All threads are ready...");
startSignal.countDown(); // 发出开始信号
try {
doneSignal.await(); // 等待所有线程完成
System.out.println("All threads have finished.");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
在这个例子中,所有线程会等待开始信号,然后同时开始执行任务。
总结
CountDownLatch
的产生意义在于提供了一种简洁高效的方式来实现线程同步、控制任务执行顺序和实现一次性事件。它简化了多线程编程中的一些常见问题,使得代码更加清晰易读。