假设我ExecutorService
在守护线程池上有一个操作(这样我不需要明确关闭它):
final var executor = Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors(),
r -> {
final var t = new Thread(r);
t.setDaemon(true);
return t;
}
);
— 我想Future
在一定超时后取消一项长时间运行的任务:
final var future = executor.submit(() -> {
try {
TimeUnit.SECONDS.sleep(Long.MAX_VALUE);
return "42";
} catch (final InterruptedException ie) {
System.out.println("Task cancelled.");
throw ie;
}
});
try {
System.out.println(future.get(5L, TimeUnit.SECONDS));
} catch (final TimeoutException ignored) {
System.out.println("Timed out, cancelling...");
future.cancel(true);
}
运行时,此代码将在大约 5 秒内产生以下输出:
Timed out, cancelling...
Task cancelled.
这意味着当前运行任务的线程确实会被中断,并且Thread.sleep()
会抛出一个InterruptedException
。现在,假设我想改用CompletableFuture
API。以下是语义上相同的代码:
final var future = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(Long.MAX_VALUE);
return "42";
} catch (final InterruptedException ie) {
System.out.println("Task cancelled.");
throw new RuntimeException(ie);
}
}, executor);
或者说我是这么想的。然而,第二版代码只会产生以下输出:
Timed out, cancelling...
后台执行线程将继续运行 Thread.sleep()
,而不会被打断,因此这两个版本的代码在语义上并不相同。
同样,如果我调用future.orTimeout(timeout, unit)
而不是future.get(timeout, unit)
,后台线程也不会被中断。后台线程将继续运行(可能)计算量大的任务,直到 JVM 关闭,而且似乎没有办法中断它们。
中断后台线程的唯一方法是调用executor.shutdownNow()
(仅仅这样executor.shutdown()
做是不够的),但这当然不是一个解决方案,因为我想在任务取消后重用执行器。
问题:
- 如何取消
CompletableFuture
并中断相应的工作线程,以及 - 这种行为有记录吗?
编辑:有一个类似的问题:如何取消 Java 8 可完成的未来?
可完成的未来 API 并不期望您使用中断来控制程序流程。
CompletableFuture#cancel方法接受一个
mayInterruptIfRunning
参数,但 api 文档指出mayInterruptIfRunning-该值在该实现中无效,因为不使用中断来控制处理。
本质上,任何等待该 Future 的操作都会因异常而停止等待。但任务本身并不知道这一点。