Java子线程如何优雅地通知主线程任务完成?
高效的多线程编程需要子线程在任务完成后通知主线程。本文介绍两种常用的方法:CompletableFuture
和 CountDownLatch
,并着重强调线程安全和最佳实践。
方法一:使用 CompletableFuture
CompletableFuture
提供了一种简洁的方式来处理异步操作,并方便地等待多个子线程完成。CompletableFuture.allOf()
方法可以等待所有给定的 CompletableFuture
对象完成。
代码示例:
List> futures = new ArrayList<>();
// 创建并启动子线程
for (int i = 0; i < 2; i++) {
futures.add(CompletableFuture.runAsync(() -> {
// 模拟子线程任务
System.out.println("子线程 " + i + " 执行完成");
}));
}
// 等待所有子线程完成
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
// 主线程处理数据
System.out.println("所有子线程执行完成,开始处理数据");
}
方法二:使用 CountDownLatch
CountDownLatch
是一种同步工具类,可以用来等待一组事件的完成。初始化时设置计数器,每个子线程完成任务后调用 countDown()
方法将计数器减一,主线程调用 await()
方法等待计数器变为零。
代码示例:
final CountDownLatch latch = new CountDownLatch(2); // 初始化计数器为 2
// 创建并启动子线程
for (int i = 0; i < 2; i++) {
new Thread(() -> {
// 模拟子线程任务
System.out.println("子线程 " + i + " 执行完成");
latch.countDown(); // 计数器减 1
}).start();
}
// 主线程等待子线程完成
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 主线程处理数据
System.out.println("所有子线程执行完成,开始处理数据");
}
重要提示:
ArrayList
不是线程安全的。在并发环境下,建议使用 CopyOnWriteArrayList
来替代,以避免数据竞争。 如果需要在子线程中修改共享变量,则必须使用合适的同步机制,例如 volatile
关键字或锁机制,以确保线程安全。CompletableFuture
提供了 exceptionally()
方法来处理异常。 CountDownLatch
需要在 await()
方法中捕获 InterruptedException
。选择哪种方法取决于具体的应用场景。CompletableFuture
更适用于需要处理异步结果的情况,而 CountDownLatch
更适用于简单的等待多个线程完成的情况。 记住始终优先考虑线程安全和健壮的异常处理。