我想在后台运行一些定期任务,我想做得对。
因此,我使用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
所以这里发生的事情......正如预期的那样,执行官正在关闭。它允许当前任务按预期完成其工作。我们看到执行程序确实完成了关闭,但我们的ScheduledFuture
没有被取消,其get()
方法仍然被阻止,监视程序线程阻止JVM退出并永久挂起。
当然有一些解决方法。例如,我可以在关机之前手动调用future.cancel(false)
或者使watchdog
成为守护程序线程,或者甚至尝试自己安排Executor的关闭,这样它就不会与正在运行的任务重叠......但是以上所有都有缺点,当代码变得更加复杂时,事情可能会横向发展。
无论如何,我正在寻求你的专家意见,因为在我明白为什么它不应该表现出来之前我会没有和平。如果它是jdk中的错误,则必须报告。如果我误解了某些内容并且我的代码错了,我必须知道它......
提前致谢
答案 0 :(得分:3)
首先要理解的是,对于定期任务,正常完成不会将Future
的状态转换为done
,因为它预计会重新安排,可能会重新运行。
这会干扰Executor.shutdown
的语义;它将cancel
所有待处理的任务,但让当前正在运行的任务完成。因此,您当前正在运行的任务正常完成,并且未将其状态设置为done
,因为它从未执行过,但由于执行程序已关闭,因此不会重新安排。
即使您使用shutdownNow
,它也会中断当前正在运行的任务但不会取消它们,因为您的任务捕获InterruptedException
并提前完成,但通常情况下,状态不会转换为{{1 }}
添加取消甚至在关机时正常完成的任务所需行为的最佳位置是done
实施;只需更改行
Executor
到
private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();