【发布时间】:2015-03-22 20:52:23
【问题描述】:
我想在后台运行一些周期性任务,我想把它做对。
所以我用ScheduledExecutorService.scheduleWithFixedDelay(..) 安排我的任务并在单独的线程上调用ScheduledFuture.get() 来控制任务的生命周期,从中捕获未捕获的异常并在任务被取消时得到通知。
问题在于,如果在任务执行时调用ScheduledExecutorService.shutdown(),则ScheduledFuture 不会收到通知,并且其get() 方法将永远处于阻塞状态。
下面是说明问题的简单代码:
public final class SomeService {
private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
private final SomePeriodicTask task = new SomePeriodicTask();
private ScheduledFuture<?> future;
public void start() {
future = executor.scheduleWithFixedDelay(task, 0, 1, TimeUnit.SECONDS);
final Runnable watchdog = new Runnable() {
@Override
public void run() {
try {
future.get();
} catch (CancellationException ex) {
System.out.println("I am cancelled");
} catch (InterruptedException e) {
System.out.println("I am interrupted");
} catch (ExecutionException e) {
System.out.println("I have an exception");
}
System.out.println("Watchdog thread is exiting");
}
};
new Thread(watchdog).start();
}
public void shutdownAndWait() {
System.out.println("Shutdown requested");
executor.shutdown();
try {
executor.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
} catch (InterruptedException e) { //BTW When and why could this happen?
System.out.println("Waiting for shutdown was interrupted");
}
System.out.println("Executor is shutdown " + executor.isShutdown());
}
}
第一,快速返回的简单任务
final class SomePeriodicTask implements Runnable {
@Override
public void run() {
System.out.print("I am just doing my job...");
System.out.println("done");
}
}
如果我们像这样运行它
public static void main(String[] args) throws InterruptedException {
SomeService service = new SomeService();
service.start();
Thread.sleep(3000);
service.shutdownAndWait();
System.out.println("Future is cancelled " + service.future.isCancelled());
System.out.println("Future is done " + service.future.isDone());
}
那么输出就是
I am just doing my job...done
I am just doing my job...done
I am just doing my job...done
Shutdown requested
I am cancelled
Watchdog thread is exiting
Executor is shutdown true
Future is cancelled true
Future is done true
完全符合预期。
但是如果我们修改任务来模拟一些繁重的工作
final class SomePeriodicTask implements Runnable{
@Override
public void run() {
System.out.print("I am just doing my job...");
try {
Thread.sleep(1500); //Heavy job. You can change it to 5000 to be sure
} catch (InterruptedException e) {
System.out.println("Task was interrupted");
}
System.out.println("done");
}
}
以便在任务执行时调用shutdown()...那么输出是
I am just doing my job...done
I am just doing my job...Shutdown requested
done
Executor is shutdown true
Future is cancelled false
Future is done false
所以这里发生了什么......执行程序正在关闭,正如预期的那样。它让当前任务按预期完成其工作。我们看到 executor 确实完成了关闭,但我们的 ScheduledFuture 没有被取消,它的 get() 方法仍然被阻塞,并且看门狗线程阻止 JVM 退出并永远挂起。
当然有解决方法。例如,我可以在关闭之前手动调用future.cancel(false) 或使watchdog 成为守护线程,甚至尝试自行安排关闭Executor 以使其不与正在运行的任务重叠......但以上所有内容都有缺点,当代码会变得更复杂的事情可以横盘整理。
无论如何,我正在寻求您的专家意见,因为在我明白为什么它的行为不应该如此之前,我将无法平静。如果是 jdk 中的错误,则必须报告。如果我误解了某些东西并且我的代码有错误,我必须知道它......
提前致谢
【问题讨论】:
标签: java multithreading concurrency