1

我目前正在重构一段代码,几乎是这样的:

public static void main(String... args) {
    while (true) {
        // Do something...
        Thread.sleep(300000); // Every 5 minutes
    }
}

这个想法是我想使用 aScheduledExecutorService来运行代码:

public static void main(String... args) {
    ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
    service.scheduleAtFixedRate(new Task(...), 0, 5, TimeUnit.MINUTES);
}

不过,我对此有一些担忧:首先,程序main()在调度后“终止”(如返回),但 JVM 保持活动状态,因为它ScheduledExecutorService仍在运行。我相信这是故意的,为了保持旧代码的“一直运行直到从外部终止”的行为,但我相信我必须安装一个关闭挂钩以确保ScheduledExecutorService正确关闭。真的会是这样,还是“从外部杀死”也会阻止执行关闭挂钩?

其次,我也对首先有一个关闭钩子感到不安。如果我有类似的代码

try {
    while (true) {}
} finally {
    // Shutdown hook code goes here instead
    service.shutdown();
    if (!service.awaitTermination(1, TimeUnit.SECONDS)) {
        service.shutdownNow();
    }
}

是否还会存在与上述相同的“从外部杀死”的担忧?

编辑:是的,原始程序循环通过而没有打破循环;它只是从 SIGINT (终端中的 control-C)终止。这是一个定义不明确的终止条件,但这是我现在唯一拥有的东西......

4

3 回答 3

2

您展示的两种方法之间存在根本区别。这while(true)是一个同步过程。当您在其中执行例程时,您可以控制该主线程上的任务。建议的重构方法是异步执行任务,因为您正在将任务卸载到执行程序(因此是一个新线程)。

正如其他评论中提到的,执行程序中的线程是非守护进程创建的。这意味着当在应用程序上调用关闭时,它将等待线程退出,然后再完成最终关闭。

所以为了处理这个问题,你可以做几件事来处理你的执行器的干净关闭。做另一篇文章中提到的第一件事,你需要找到你的终止条件。

其次,您需要评估您的流程并查看您是否正在做任何无法在流程中停止的关键操作(例如写入磁盘上的文件)。

如果您没有做任何关键的事情(这是这里的重要部分),您可以传递 aThreadFactory返回守护线程。这将使JVM终止进程,无论它是否完成。工厂看起来像这样(我还为其添加了名称,因为它总是可以为您的线程命名,这样更容易调试)。

public class DaemonThreadFactory implements ThreadFactory {

    private final String NAME;

    public DaemonThreadFactory() {
        this(null);
    }

    public DaemonThreadFactory(final String name) {
        NAME = name;
    }

    @Override
    public Thread newThread(Runnable r) {
        Thread t = new Thread(r);
        t.setDaemon(true);
        if(NAME != null) {
        t.setName(NAME);
        }
        return t;
    }
}

您可以处理它的另一种方法是保留ScheduledFutureservice.scheduleAtFixedRate. 使用此对象,您可以在认为合适时取消任务并在适当的时候关闭执行程序。

private void test() {
        ScheduledExecutorService exec = Executors.newSingleThreadScheduledExecutor();

        ScheduledFuture<?> future =  exec.scheduleAtFixedRate(new Task(), 0, 5, TimeUnit.MINUTES);

        //if you want to cancel the task
        //boolean is if you want to interrupt while running
        future.cancel(true);
    }

您仍然需要关闭执行程序才能清理干净,但此时应该没有任何运行

如果由于Future某种原因该对象是不可能的,如果您希望应用程序关闭,您将需要使用 shutdown 和 shutdownNow。如果你正在做一些关键的事情,你将需要一个关闭钩子或在任务中进行异步调用,以便正确清理它正在做的任何事情并优雅地退出。

于 2014-08-08T19:00:08.753 回答
1

当你的 Java 进程被杀死时,线程不会收到异常;他们会突然死去。因此,您的finally方法将无济于事。关闭挂钩将运行,但如果您被 SIGKILL 杀死,则不会运行。但是,关闭钩子将无法做很多事情,因为ExecutorService' 的池线程可能会在那时死掉。

换句话说,您最好寻找一种更高级别的方法来要求您的 Java 程序完成。

请记住,具有无限循环的当前代码ExecutorService在终止信号时的行为方面与基于 - 的解决方案处于同等地位。

于 2014-08-08T16:44:24.053 回答
0

按照设计,执行程序默认运行在非守护线程中。这意味着您需要某种方式来告诉执行程序终止,否则您的程序将不会退出。

您需要考虑您希望程序在哪些条件下退出。然后,在这种情况下,安排执行器关闭。

于 2014-08-08T16:21:20.267 回答