Java 执行器关闭后,ScheduledFuture.get()仍被阻止
我想在后台运行一些定期任务,我想把它做好。Java 执行器关闭后,ScheduledFuture.get()仍被阻止,java,multithreading,concurrency,Java,Multithreading,Concurrency,我想在后台运行一些定期任务,我想把它做好。 因此,我使用ScheduledExecutorService.scheduleWithFixedDelay(..)调度任务,并在单独的线程上调用ScheduledFuture.get(),以控制任务的生命周期,捕获任务中未捕获的异常,并在任务取消时收到通知 问题是,如果在任务执行时调用了ScheduledExecutorService.shutdown(),则不会收到通知,其ScheduledFuture方法将永远被阻止 下面是说明问题的简单代码: p
因此,我使用
ScheduledExecutorService.scheduleWithFixedDelay(..)
调度任务,并在单独的线程上调用ScheduledFuture.get()
,以控制任务的生命周期,捕获任务中未捕获的异常,并在任务取消时收到通知
问题是,如果在任务执行时调用了ScheduledExecutorService.shutdown()
,则不会收到通知,其ScheduledFuture
方法将永远被阻止
下面是说明问题的简单代码:
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());
}
}
如果我们像这样运行它
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中的一个bug,则必须报告它。如果我误解了什么,我的代码是错误的,我必须知道它
提前感谢首先要了解的是,对于周期性任务,正常完成不会将未来的状态变为完成,因为它预期会重新安排并可能重新运行 这会干扰执行器.shutdown的语义;它将取消所有挂起的任务,但让当前正在运行的任务完成。因此,当前正在运行的任务正常完成,不会像以前那样将其状态设置为
done
,但不会重新安排,因为执行器已关闭
即使您使用shutdownow
它也会中断当前正在运行的任务,但不会取消它们,而且由于您的任务捕获了InterruptedException
,并在正常情况下提前完成,因此不会有状态转换到done
添加所需行为(即使在关机时正常完成的任务也会被取消)的最佳位置是Executor
实现;换线就行了
private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
到
private final ScheduledExecutorService executor=new ScheduledThreadPoolExecutor(1){
@凌驾
执行后受保护的无效(可运行的r、可丢弃的t){
if(t==null&&IsShutton()&&r RunnableScheduledFuture的实例)
{
RunnableScheduledFuture rsf=(RunnableScheduledFuture)r;
如果(rsf.isPeriodic())rsf.cancel(false);
}
};
};
@Holder非常感谢您的帮助,但这并没有回答我的问题。问题是,为什么ScheduledFuture.get()会被无限期地阻止,即使执行器很早就关闭并消失了。它应该返回null或抛出某种异常或任何您命名的异常,但不能永远挂在那里。这种行为没有得到适当的记录,在我看来确实是错误的。我倾向于认为它是一个错误。我打印未来的状态只是为了详细说明,以表明未来不会收到任何关机通知。我不在乎它的实际……状态。也许这让人困惑,我应该把它删掉。您的解决方案也是一种变通方法。我在问题中提到了其中三个,你提出了第四个。肯定还有更多,但我仍然想知道为什么ScheduledFuture.get()
在关机时会被严重阻止,以及为什么它被认为是正确的行为。我确实解释了这种行为。你是否关心未来的状况并不重要,但未来关心你。它的get
方法永远不会返回,只要未来没有完成。而且周期性的未来不会完成。如果你不想解决问题,就不要创建“看门狗”。对于定期任务的未来,不要调用get
,因为很明显,它永远无法正常完成。
private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
private final ScheduledExecutorService executor = new ScheduledThreadPoolExecutor(1) {
@Override
protected void afterExecute(Runnable r, Throwable t) {
if(t==null && isShutdown() && r instanceof RunnableScheduledFuture<?>)
{
RunnableScheduledFuture<?> rsf = (RunnableScheduledFuture<?>)r;
if(rsf.isPeriodic()) rsf.cancel(false);
}
};
};