主线程的等待之道:如何确保Java中的子线程全部执行完毕
在Java多线程编程中,常常需要让主线程等待所有子线程执行完毕再继续执行后续操作。这种需求通常出现在需要收集多线程任务的结果或者需要确保资源的正确释放时。本文将深入探讨几种实现主线程等待子线程的技术,从最基本的Thread.join()
到高级的并发工具类。
1. 使用Thread.join()
Thread.join()
方法是Java中最基本的线程同步机制之一。它可以让主线程等待子线程完成后再继续执行。join()
方法会阻塞调用线程,直到目标线程终止。
示例
public class JoinExample {
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
try {
Thread.sleep(1000);
System.out.println("Thread 1 completed");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
Thread thread2 = new Thread(() -> {
try {
Thread.sleep(2000);
System.out.println("Thread 2 completed");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
thread1.start();
thread2.start();
try {
thread1.join(); // 等待thread1完成
thread2.join(); // 等待thread2完成
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("All threads have completed execution");
}
}
注意事项
- 阻塞:
join()
会阻塞调用线程,直到目标线程终止。因此,使用join()
需要确保子线程能够及时结束。 - 异常处理:
join()
方法会抛出InterruptedException
,需要进行适当的异常处理。
2. 使用ExecutorService
和awaitTermination()
当使用线程池来管理线程时,可以通过ExecutorService
提供的awaitTermination()
方法来等待所有线程任务完成。
示例
import java.util.concurrent.*;
public class ExecutorServiceExample {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(2);
Runnable task1 = () -> {
try {
Thread.sleep(1000);
System.out.println("Task 1 completed");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
};
Runnable task2 = () -> {
try {
Thread.sleep(2000);
System.out.println("Task 2 completed");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
};
executorService.submit(task1);
executorService.submit(task2);
executorService.shutdown(); // 关闭线程池
try {
if (!executorService.awaitTermination(5, TimeUnit.SECONDS)) {
System.out.println("Tasks did not finish in the specified time");
executorService.shutdownNow(); // 强制关闭
}
} catch (InterruptedException e) {
executorService.shutdownNow();
}
System.out.println("All tasks have completed execution");
}
}
注意事项
- 线程池管理:
ExecutorService
提供了更高级的线程管理功能,适用于需要动态管理线程数量的场景。 - 超时设置:
awaitTermination()
允许设置等待超时时间,避免主线程无限期等待。
3. 使用CountDownLatch
CountDownLatch
是Java并发包中一个非常有用的工具类,它允许一个或多个线程等待其他线程完成操作。通过一个计数器来跟踪需要等待的线程数量,当计数器达到零时,所有等待的线程将被唤醒。
示例
import java.util.concurrent.CountDownLatch;
public class CountDownLatchExample {
public static void main(String[] args) {
int numThreads = 2;
CountDownLatch latch = new CountDownLatch(numThreads);
Runnable task = () -> {
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " completed");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
latch.countDown(); // 计数器减一
}
};
for (int i = 0; i < numThreads; i++) {
new Thread(task, "Thread " + (i + 1)).start();
}
try {
latch.await(); // 等待计数器变为零
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("All threads have completed execution");
}
}
注意事项
- 适用场景:
CountDownLatch
非常适合用于需要等待一组任务完成的场景,如并行计算、任务分发等。 - 不可重用:
CountDownLatch
的计数器在到达零后无法重置,如果需要重用计数器,考虑使用CyclicBarrier
。
4. 使用CyclicBarrier
CyclicBarrier
是Java中的另一种同步工具,它允许一组线程在达到某个屏障点时相互等待。与CountDownLatch
不同的是,CyclicBarrier
可以被重置并再次使用。
示例
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierExample {
public static void main(String[] args) {
int numThreads = 3;
CyclicBarrier barrier = new CyclicBarrier(numThreads, () -> {
System.out.println("All threads reached the barrier");
});
Runnable task = () -> {
try {
System.out.println(Thread.currentThread().getName() + " is waiting at the barrier");
barrier.await(); // 等待所有线程到达屏障点
System.out.println(Thread.currentThread().getName() + " passed the barrier");
} catch (InterruptedException | BrokenBarrierException e) {
Thread.currentThread().interrupt();
}
};
for (int i = 0; i < numThreads; i++) {
new Thread(task, "Thread " + (i + 1)).start();
}
}
}
注意事项
- 重用能力:
CyclicBarrier
可以被重置并再次使用,适合用于多次同步操作的场景。 - 屏障操作:可以通过提供
Runnable
参数来在所有线程到达屏障点时执行特定操作。
5. 使用CompletableFuture
CompletableFuture
是Java 8引入的增强型Future
,它支持异步编程和组合式任务管理。通过CompletableFuture
,可以轻松实现异步任务的等待和结果收集。
示例
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class CompletableFutureExample {
public static void main(String[] args) {
CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> {
try {
Thread.sleep(1000);
System.out.println("Task 1 completed");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
CompletableFuture<Void> future2 = CompletableFuture.runAsync(() -> {
try {
Thread.sleep(2000);
System.out.println("Task 2 completed");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
CompletableFuture<Void> allOf = CompletableFuture.allOf(future1, future2);
try {
allOf.get(); // 阻塞等待所有任务完成
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
System.out.println("All tasks have completed execution");
}
}
注意事项
- 组合式任务:
CompletableFuture
支持链式编程和任务组合,适合用于复杂的异步流程。 - 异常处理:提供了灵活的异常处理机制,可以通过
exceptionally
等方法进行处理。
总结
在Java中,主线程等待子线程的执行是一个常见的需求。根据具体场景的不同,可以选择不同的实现方式:
Thread.join()
:适用于简单的线程等待场景。ExecutorService
和awaitTermination()
:适用于线程池管理的场景。CountDownLatch
:适用于一次性任务的等待。CyclicBarrier
:适用于需要重用的同步操作。CompletableFuture
:适用于异步编程和复杂的任务组合。
通过合理选择和组合这些工具,可以实现高效和健壮的多线程程序,确保主线程在适当的时机继续执行。