
本文深入探讨了`executors.newsinglethreadexecutor()`在保证任务顺序执行方面的特性,并解释了为何在多线程环境下,主线程的输出可能与单线程执行器中任务的输出顺序不一致。核心在于主线程与执行器线程的独立性。文章提供了标准的`shutdownandawaittermination`模式,以确保主线程等待所有提交任务完成后再继续执行,从而实现预期的输出顺序和正确的资源管理。
Executors.newSingleThreadExecutor() 创建一个使用单个工作线程的 ExecutorService。其核心保证是:所有提交到此执行器的任务都将按照它们被提交的顺序,在一个单独的线程中依次执行。这意味着,如果你提交了任务A、B、C,那么它们一定会以A -> B -> C的顺序被该工作线程执行,绝不会并发或乱序。
然而,这种顺序保证仅限于执行器内部的任务队列。它并不意味着提交任务的主线程会等待这些任务执行完毕。主线程在提交任务后会立即继续执行其自身的代码,而执行器中的任务则在后台异步执行。这种行为可能导致主线程的输出与执行器任务的输出在控制台上的顺序出现“错位”。
考虑以下示例代码:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ZooInfo {
public static void main(String[] args) {
ExecutorService executorService = null;
Runnable runnable1 = () -> System.out.println("Printing zoo inventory");
Runnable runnable2 = () -> {
for (int i = 0; i < 3; i++) {
System.out.println("Printing record " + i);
}
};
try {
executorService = Executors.newSingleThreadExecutor();
System.out.println("Begin"); // 主线程输出
executorService.execute(runnable1);
executorService.execute(runnable2);
executorService.execute(runnable1);
System.out.println("End."); // 主线程输出
} finally {
if (executorService != null) {
executorService.shutdown();
}
}
}
}运行上述代码,你可能会观察到类似以下的输出:
立即学习“Java免费学习笔记(深入)”;
Begin End. Printing zoo inventory Printing record 0 Printing record 1 Printing record 2 Printing zoo inventory
尽管 runnable1 和 runnable2 任务在执行器内部是按提交顺序执行的,但主线程的 System.out.println("End.") 却在这些任务的输出之前显示。这并非 newSingleThreadExecutor() 没有保证顺序,而是主线程没有等待执行器完成其工作。
为了确保主线程在所有提交的任务完成后再继续执行(例如打印“End.”),我们需要一种机制来同步主线程和执行器线程。ExecutorService 提供了 shutdown() 和 awaitTermination() 方法来实现这一目的。
将这两个方法结合起来,形成一个标准的“关闭并等待终止”模式,可以有效解决主线程与执行器线程的同步问题。
通常,我们会封装一个辅助方法来实现 shutdownAndAwaitTermination 逻辑:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class ZooInfoCorrected {
public static void main(String[] args) {
ExecutorService executorService = null;
Runnable runnable1 = () -> System.out.println("Printing zoo inventory");
Runnable runnable2 = () -> {
for (int i = 0; i < 3; i++) {
System.out.println("Printing record " + i);
}
};
try {
executorService = Executors.newSingleThreadExecutor();
System.out.println("Begin");
executorService.execute(runnable1);
executorService.execute(runnable2);
executorService.execute(runnable1);
// 关键步骤:等待执行器任务完成
shutdownAndAwaitTermination(executorService);
System.out.println("End.");
} finally {
// 确保执行器被关闭,即使在等待过程中发生异常
// 如果shutdownAndAwaitTermination已经调用了shutdown,这里可以省略,
// 但为了健壮性,在finally中再次检查并调用是安全的。
if (executorService != null && !executorService.isShutdown()) {
executorService.shutdown();
}
}
}
/**
* 关闭ExecutorService并等待其所有任务完成。
* 这是一个推荐的boilerplate模式。
*
* @param pool 要关闭的ExecutorService
*/
static void shutdownAndAwaitTermination(ExecutorService pool) {
pool.shutdown(); // 禁用新任务提交
try {
// 等待已提交任务在指定超时时间内完成
if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
pool.shutdownNow(); // 如果超时,则强制关闭
// 等待任务响应中断
if (!pool.awaitTermination(60, TimeUnit.SECONDS))
System.err.println("Pool did not terminate");
}
} catch (InterruptedException ie) {
// (可选) 重新中断当前线程
Thread.currentThread().interrupt();
// 如果当前线程在等待时被中断,则强制关闭
pool.shutdownNow();
System.err.println("Current thread interrupted while waiting for pool termination.");
}
}
}通过在 executorService.execute(runnable1); 之后调用 shutdownAndAwaitTermination(executorService);,主线程会阻塞,直到执行器中的所有任务完成,或者达到超时时间。这样,"End." 就会在所有任务输出之后打印:
Begin Printing zoo inventory Printing record 0 Printing record 1 Printing record 2 Printing zoo inventory End.
Executors.newSingleThreadExecutor() 确实保证了其内部任务的顺序执行。然而,当观察到主线程的输出与执行器任务的输出顺序不符时,这通常是因为主线程没有等待执行器完成其工作。通过采用标准的 shutdownAndAwaitTermination 模式,我们可以有效地同步主线程与执行器线程,确保所有后台任务完成后,主线程再继续执行,从而实现预期的程序行为和输出顺序。正确管理 ExecutorService 的生命周期是编写健壮并发应用程序的关键。
以上就是Java newSingleThreadExecutor 顺序执行与主线程同步的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号