我正在尝试学习Java的并发API。下面是一个示例程序。
class WaitTest {
public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {
ExecutorService executorService = null;
try {
executorService = Executors.newSingleThreadExecutor();
Future<?> future = executorService.submit(() ->
{
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Printing " + i);
}
});
future.get(5, TimeUnit.SECONDS);
System.out.println("Reached successfully");
} finally {
if (executorService != null) {
executorService.shutdown();
}
}
}
}
提供给ExecutorService的Runnable任务需要10秒钟才能完成。我将超时设置为5秒,以从将来的对象获取结果。显然,由于抛出了TimeoutException,主要方法在5秒后退出。但是即使主方法退出后,Runnable任务仍继续执行。
这是输出。
Printing 0
Printing 1
Printing 2
Printing 3
Printing 4
Printing 5
Printing 6
Printing 7
Printing 8
Printing 9
Printing 10
Printing 11
Printing 12
Printing 13
Printing 14
Printing 15
Printing 16
Printing 17
Printing 18
Printing 19
Printing 20
Printing 21
Printing 22
Printing 23
Printing 24
Printing 25
Printing 26
Printing 27
Printing 28
Printing 29
Printing 30
Printing 31
Printing 32
Printing 33
Printing 34
Printing 35
Printing 36
Printing 37
Printing 38
Printing 39
Printing 40
Printing 41
Printing 42
Printing 43
Exception in thread "main" java.util.concurrent.TimeoutException
at java.util.concurrent.FutureTask.get(FutureTask.java:205)
at ocp.WaitTest.main(ConcurrencyTest.java:89)
Printing 44
Printing 45
Printing 46
Printing 47
Printing 48
Printing 49
Printing 50
Printing 51
Printing 52
Printing 53
Printing 54
Printing 55
Printing 56
Printing 57
Printing 58
Printing 59
Printing 60
Printing 61
Printing 62
Printing 63
Printing 64
Printing 65
Printing 66
Printing 67
Printing 68
Printing 69
Printing 70
Printing 71
Printing 72
Printing 73
Printing 74
Printing 75
Printing 76
Printing 77
Printing 78
Printing 79
Printing 80
Printing 81
Printing 82
Printing 83
Printing 84
Printing 85
Printing 86
Printing 87
Printing 88
Printing 89
Printing 90
Printing 91
Printing 92
Printing 93
Printing 94
Printing 95
Printing 96
Printing 97
Printing 98
Printing 99
有什么想法吗?
答案 0 :(得分:4)
发生了一些事情。首先,Executors.newSingleThreadExecutor()
使用的线程是non-daemon线程。正如Thread
的文档所述,非守护进程线程将使JVM保持活动状态。
Java虚拟机启动时,通常只有一个非守护线程(通常调用某些指定类的名为
main
的方法)。 Java虚拟机将继续执行线程,直到发生以下两种情况之一为止:
- 已调用类
exit
的{{1}}方法,并且安全管理器已允许进行退出操作。- 不是所有守护进程线程的所有线程都已死亡,或者是通过从调用返回到
Runtime
方法,或者是抛出了传播到run
方法之外的异常。
第二,run
不会取消任何排队的或当前正在执行的任务。这只是ExecutorService.shutdown()
不再接受新任务并在所有现有任务完成后终止的信号。来自the Javadoc:
启动有序关闭,在该关闭中执行先前提交的任务,但不接受任何新任务。如果已关闭,则调用不会产生其他影响。
此方法不等待先前提交的任务完成执行。使用awaitTermination可以做到这一点。
如果您想立即终止ExecutorService
,则必须使用ExecutorService.shutdownNow()
。
尝试停止所有正在执行的任务,暂停正在等待的任务的处理,并返回正在等待执行的任务的列表。
此方法不等待主动执行的任务终止。使用awaitTermination可以做到这一点。
除了尽力而为后,无法保证停止处理正在执行的任务。例如,典型的实现将通过Thread.interrupt()取消,因此任何无法响应中断的任务都可能永远不会终止。
正如Javadoc所述,不能保证即使使用ExecutorService
,正在执行的任务也会终止。开发人员必须对任务进行编码以响应中断。
这导致第三件事:您的任务没有响应中断。尽管在线程中断时shutdownNow
会抛出一个Thread.sleep
,但在抛出该异常时您不会跳出循环;您的代码仅打印堆栈跟踪,然后继续进行下一个迭代。要解决此问题,请在InterruptedException
块的末尾添加一个break
语句。
您还可以选择通过catch
使用自定义ThreadFactory
。如果您拥有工厂返回的 daemon 线程,那么一旦主返回,JVM将退出。
答案 1 :(得分:0)
shutdownNow()
会尝试中断线程,但是您正在睡眠中捕获中断异常。因此,您应该将整个代码放在try catch块中。
这可能对您有帮助
public class Test {
public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {
ExecutorService executorService = null;
try {
executorService = Executors.newSingleThreadExecutor();
Future<?> future = executorService.submit(() ->
{
try {
for (int i = 0; i < 100; i++) {
Thread.sleep(100);
System.out.println("Printing " + i);
}
} catch (Exception e) {
System.out.println("Interrupted");
}
});
future.get(5, TimeUnit.SECONDS);
System.out.println("Reached successfully");
} finally {
if (executorService != null) {
executorService.shutdownNow();
}
}
}
}
答案 2 :(得分:0)
正在运行TimeoutException
函数的线程中发生了main()
,而不是执行者服务正在执行的线程中发生了
因此,尽管运行主函数的线程已完成执行,但JVM仍将等待所有其他正在执行的线程(在本例中为打印数字的线程)终止。