简体   繁体   English

Executor关闭后,ScheduledFuture.get()仍然被阻止

[英]ScheduledFuture.get() is still blocked after Executor shutdown

I want to run some periodic task in background and I want to do it right. 我想在后台运行一些定期任务,我想做得对。
So I schedule my task with ScheduledExecutorService.scheduleWithFixedDelay(..) and call ScheduledFuture.get() on the separate thread to control the life of the task, catch uncaught exceptions from it and get notified if the task is cancelled. 因此,我使用ScheduledExecutorService.scheduleWithFixedDelay(..)安排我的任务,并在单独的线程上调用ScheduledFuture.get()来控制任务的生命周期,从中捕获未捕获的异常,并在任务被取消时得到通知。

The problem is that if ScheduledExecutorService.shutdown() is called while the task is executing, than ScheduledFuture does not get notified and its get() method stays blocked forever. 问题是,如果ScheduledExecutorService.shutdown()而任务执行被称为比ScheduledFuture没有得到通知其get()方法永远撑封锁。

And here comes the simple code to illustrate the problem: 这里有简单的代码来说明问题:

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());
    }
}

First, simple task which returns quickly 首先,快速返回的简单任务

final class SomePeriodicTask implements Runnable {
    @Override
    public void run() {
        System.out.print("I am just doing my job...");
        System.out.println("done");
    }
}

If we run it like this 如果我们像这样运行它

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());
}

then the output is 那么输出就是

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

totally as expected. 完全符合预期。

But if we modify the task to simulate some heavy job 但是如果我们修改任务来模拟一些繁重的工作

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");
    }
}

so that the call to shutdown() happens while the task is executing... then the output is 这样在执行任务时就会发生对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

So what happened here... Executor is shutting down, as expected. 那么这里发生的事情......正如预期的那样,执行官正在关闭。 It lets the current task to finish its job, as expected. 它允许当前任务按预期完成其工作。 We see that executor did finish shutting down, but our ScheduledFuture did not get cancelled and its get() method is still blocked and the watchdog thread prevents JVM from exiting and hangs forever. 我们看到执行程序确实完成了关闭,但是我们的ScheduledFuture没有被取消,它的get()方法仍然被阻止,监视程序线程阻止JVM退出并永久挂起。

Of course there are workarounds. 当然有解决方法。 For example I can call future.cancel(false) manually before shutdown or make watchdog a daemon thread or even try to schedule shutdown of Executor by himself so that it does not overlap with running task... But all of above have drawbacks and when code will get more complicated things can go sideways. 例如,我可以在关机之前手动调用future.cancel(false)或者使watchdog成为守护程序线程,或者甚至尝试自己安排Executor的关闭,这样它就不会与正在运行的任务重叠......但是以上所有都有缺点,当代码将变得更加复杂,可以横向进行。

And anyway, I am seeking for your expert opinion because I will have no peace until I understand why it doesn't behave like it should. 无论如何,我正在寻求你的专家意见,因为在我理解为什么它不应该表现出来之前我会没有和平。 If it is a bug in jdk it must be reported. 如果它是jdk中的错误,则必须报告。 If I misunderstand something and my code is wrong, I must know it... 如果我误解了某些内容并且我的代码错了,我必须知道它......

Thanks in advance 提前致谢

The first thing to understand is that for a periodic task, a normal completion does not turn the state of the Future to done as it expects to be rescheduled and possibly rerun. 要理解的第一件事是,对于周期性任务,正常完成不会使Future的状态done因为它期望重新安排并可能重新运行。

This interferes with the semantics of Executor.shutdown ; 这会干扰Executor.shutdown的语义; it will cancel all pending tasks but let the currently running tasks complete. 它将cancel所有挂起的任务,但让当前正在运行的任务完成。 So your currently running task completes normally and doesn't set its state to done as it never does, but isn't rescheduled because the executor has been shut down. 所以,你当前正在运行的任务正常完成,并且不设置自己的状态done ,因为它从来不会,但不改期,因为执行程序已关闭。

Even if you use shutdownNow it will interrupt the currently running tasks but not cancel them and since your task catches the InterruptedException and completes earlier but normally, there will be no state transition to done . 即使您使用shutdownNow它也会中断当前正在运行的任务但不会取消它们,因为您的任务捕获了InterruptedException并提前完成,但通常情况下,状态转换不会done

The best place to add the desired behavior of cancelling even tasks that completed normally upon shutdown is the Executor implementation; 添加所需的行为来取消甚至在关机时正常完成的任务的最佳位置是Executor实现; just change the line 只是换行

private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();

to

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);
    }
  };
};

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM