假设我有一个
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
和中断相应的工作线程,以及编辑:有一个类似的问题:How to cancel Java 8 completable future?
未来可完成的 API 并不期望您使用中断来控制程序的流程。
方法 CompletableFuture#cancel 接受
mayInterruptIfRunning
参数,但 api 文档声明
mayInterruptIfRunning - 该值在此实现中无效,因为不使用中断来控制处理。
本质上,任何等待未来的事情都会因异常而停止等待。但任务本身并不知道这一点。