在多线程编程中,协调各个线程的启动、完成通常是一项挑战。Java的并发API提供了多种同步辅助工具来简化这种协调,其中之一就是CountDownLatch
。本文将详细介绍CountDownLatch
的概念、使用方法,并通过实例来加深理解。
CountDownLatch
是一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。简单来说,它是一个计数器,但它的特性是,它只能倒数,不能正数。
当我们创建一个CountDownLatch
的实例时,我们指定一个整数作为计数器的初始值。每次调用countDown()
方法时,计数器的值会减1。当计数器的值减到0时,所有因await()
方法等待的线程将被唤醒并继续执行。
在深入示例之前,让我们先看一下CountDownLatch
的基本用法。它主要涉及三个操作:
CountDownLatch latch = new CountDownLatch(N);
其中N是你希望等待的操作数。latch.await();
任何调用此方法的线程都将被阻塞,直到计数器值为零。latch.countDown();
每次此方法被调用,计数器的值就会减一。假设我们有一个应用程序,它依赖于多个外部服务。在应用程序可以启动之前,所有这些外部服务都需要先启动。我们可以使用CountDownLatch
来确保这一点。
public class ServiceLauncher {
private final CountDownLatch latch;
public ServiceLauncher(int numberOfServices) {
latch = new CountDownLatch(numberOfServices);
}
public void launchService(Runnable service) {
new Thread(() -> {
service.run();
latch.countDown();
}).start();
}
public void awaitServices() throws InterruptedException {
latch.await();
System.out.println("所有服务已启动,应用程序可以继续!");
}
public static void main(String[] args) throws InterruptedException {
ServiceLauncher launcher = new ServiceLauncher(3);
launcher.launchService(() -> {
System.out.println("服务1启动中...");
// 模拟服务启动耗时
try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("服务1已启动");
});
launcher.launchService(() -> {
System.out.println("服务2启动中...");
try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("服务2已启动");
});
launcher.launchService(() -> {
System.out.println("服务3启动中...");
try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("服务3已启动");
});
launcher.awaitServices();
}
}
在这个例子中,我们创建了一个ServiceLauncher
类,它使用CountDownLatch
来确保所有服务都启动后应用程序才继续执行。每个服务启动后都会调用countDown()
来减少计数器的值。
另一个常见用例是在主线程中等待一组任务完成。例如,我们可能想要并行处理一组数据,然后在所有处理完成后继续执行。
public class TaskProcessor {
private final CountDownLatch latch;
public TaskProcessor(int numberOfTasks) {
latch = new CountDownLatch(numberOfTasks);
}
public void processTask(int taskId) {
new Thread(() -> {
System.out.println("任务 " + taskId + " 正在执行...");
// 模拟任务执行耗时
try { Thread.sleep((long)(Math.random() * 2000)); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("任务 " + taskId + " 完成");
latch.countDown();
}).start();
}
public void awaitCompletion() throws InterruptedException {
latch.await();
System.out.println("所有任务执行完毕!");
}
public static void main(String[] args) throws InterruptedException {
TaskProcessor processor = new TaskProcessor(5);
for (int i = 1; i <= 5; i++) {
processor.processTask(i);
}
processor.awaitCompletion();
}
}
在这个例子中,我们创建了一个TaskProcessor
类,它使用CountDownLatch
来确保所有任务都完成后程序才继续执行。每个任务完成后都会调用countDown()
来减少计数器的值。
CountDownLatch
的计数器无法被重置,如果需要能够重置计数的版本,可以考虑使用CyclicBarrier
。await()
方法会使当前线程等待,直到计数器达到零,但也可以指定等待时间,超时后线程将不再等待。CountDownLatch
是线程安全的,内部实现确保了计数器的正确递减和线程的安全唤醒。CountDownLatch
是Java并发工具箱中的一个非常有用的组件,它能够简化线程间的协作,确保在继续执行前特定的条件得到满足。通过上述示例,我们了解了它的基本使用方法和一些实际应用场景。在实际开发中,合理利用CountDownLatch
可以帮助我们更好地控制并发流程,提高程序的健壮性和用户体验。