【发布时间】:2019-06-16 05:37:27
【问题描述】:
我想使用 Executor 接口(使用 Callable)来启动一个 Thread(我们称之为可调用线程),它将做使用阻塞方法的工作。 这意味着当主线程调用 Future.cancel(true)(调用 Thread.interrupt() 时,可调用线程可以抛出 InterruptedException >)。
我还希望我的可调用线程在代码的取消部分中使用其他阻塞方法被中断时正确终止。
在实现这一点时,我遇到了以下行为:当我调用 Future.cancel(true) 方法时,如果主线程立即等待,则可正确通知可调用线程中断BUT对于使用 Future.get() 来终止它,可调用线程在调用任何阻塞方法时都会被杀死。
下面的JUnit 5 sn-p 说明了这个问题。 如果主线程在 cancel() 和 get() 调用之间没有休眠,我们可以轻松地重现它。 如果我们睡了一会儿但还不够,我们可以看到可调用线程完成了一半的取消工作。 如果我们睡眠充足,可调用线程会正确完成其取消工作。
注意 1:我检查了可调用线程的中断状态:它正确设置了一次且仅一次,如预期的那样。
注意2:在中断后(传入取消代码时)逐步调试我的可调用线程时,在进入阻塞方法(没有 InterruptedException 似乎被抛出)。
@Test
public void testCallable() {
ExecutorService executorService = Executors.newSingleThreadExecutor();
System.out.println("Main thread: Submitting callable...");
final Future<Void> future = executorService.submit(() -> {
boolean interrupted = Thread.interrupted();
while (!interrupted) {
System.out.println("Callable thread: working...");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
System.out.println("Callable thread: Interrupted while sleeping, starting cancellation...");
Thread.currentThread().interrupt();
}
interrupted = Thread.interrupted();
}
final int steps = 5;
for (int i=0; i<steps; ++i) {
System.out.println(String.format("Callable thread: Cancelling (step %d/%d)...", i+1, steps));
try {
Thread.sleep(200);
} catch (InterruptedException e) {
Assertions.fail("Callable thread: Should not be interrupted!");
}
}
return null;
});
final int mainThreadSleepBeforeCancelMs = 2000;
System.out.println(String.format("Main thread: Callable submitted, sleeping %d ms...", mainThreadSleepBeforeCancelMs));
try {
Thread.sleep(mainThreadSleepBeforeCancelMs);
} catch (InterruptedException e) {
Assertions.fail("Main thread: interrupted while sleeping.");
}
System.out.println("Main thread: Cancelling callable...");
future.cancel(true);
System.out.println("Main thread: Cancelable just cancelled.");
// Waiting "manually" helps to test error cases:
// - Setting to 0 (no wait) will prevent the callable thread to correctly terminate;
// - Setting to 500 will prevent the callable thread to correctly terminate (but some cancel process is done);
// - Setting to 1500 will let the callable thread to correctly terminate.
final int mainThreadSleepBeforeGetMs = 0;
try {
Thread.sleep(mainThreadSleepBeforeGetMs);
} catch (InterruptedException e) {
Assertions.fail("Main thread: interrupted while sleeping.");
}
System.out.println("Main thread: calling future.get()...");
try {
future.get();
} catch (InterruptedException e) {
System.out.println("Main thread: Future.get() interrupted: Error.");
} catch (ExecutionException e) {
System.out.println("Main thread: Future.get() threw an ExecutionException: Error.");
} catch (CancellationException e) {
System.out.println("Main thread: Future.get() threw an CancellationException: OK.");
}
executorService.shutdown();
}
【问题讨论】:
标签: java multithreading java-8 executorservice