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

时间:2015-01-23 18:23:14

标签: java multithreading concurrency

我想在后台运行一些定期任务,我想做得对。
因此,我使用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中的错误,则必须报告。如果我误解了某些内容并且我的代码错了,我必须知道它......

提前致谢

1 个答案:

答案 0 :(得分:3)

首先要理解的是,对于定期任务,正常完成不会将Future的状态转换为done,因为它预计会重新安排,可能会重新运行。

这会干扰Executor.shutdown的语义;它将cancel所有待处理的任务,但让当前正在运行的任务完成。因此,您当前正在运行的任务正常完成,并且未将其状态设置为done,因为它从未执行过,但由于执行程序已关闭,因此不会重新安排。

即使您使用shutdownNow,它也会中断当前正在运行的任务但不会取消它们,因为您的任务捕获InterruptedException并提前完成,但通常情况下,状态不会转换为{{1 }}

添加取消甚至在关机时正常完成的任务所需行为的最佳位置是done实施;只需更改行

Executor

private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();