【问题标题】:Java Concurrency: Run until terminatedJava并发:运行直到终止
【发布时间】:2014-08-09 03:24:12
【问题描述】:

我目前正在重构一段代码,大致如下:

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

想法是我想使用ScheduledExecutorService 来运行代码:

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)终止。这是一个定义不明确的终止条件,但这是我现在唯一拥有的......

【问题讨论】:

  • 在使用shutdownNow()之后,你仍然需要awaitTermination()。 “从外部杀死”我认为对于 JVM 来说是未定义的行为。我怀疑是否指定了关闭挂钩是否运行。

标签: java concurrency terminate scheduledexecutorservice shutdown-hook


【解决方案1】:

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

正如在其他 cmets 中提到的,执行程序中的线程是非守护进程创建的。这意味着当应用程序调用关闭时,它会在完成最终关闭之前等待线程退出。

因此,要处理此问题,您可以采取多种措施来彻底关闭执行程序。做另一篇文章中提到的第一件事,你需要找到你的终止条件。

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

如果你没有做任何关键的事情(这是这里的重要部分),你可以传递一个ThreadFactory,它会返回一个守护线程。这将使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;
    }
}

处理它的另一种方法是保留service.scheduleAtFixedRate 返回的ScheduledFuture。有了这个对象,你可以在你认为合适的时候取消任务并在适当的时候关闭执行器。

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 来关闭应用程序。如果你正在做一些关键的事情,你将需要一个关闭钩子或在任务中进行异步调用,以便正确清理它正在做的任何事情并优雅地退出。

【讨论】:

    【解决方案2】:

    当你的 Java 进程被杀死时,线程不会收到异常;他们会突然死去。因此,您的 finally 方法将无济于事。关闭挂钩将运行,但如果您被 SIGKILL 杀死,则不会运行。然而,由于ExecutorService 的池线程在当时可能已经死机,因此关闭钩子将无能为力。

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

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

    【讨论】:

      【解决方案3】:

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

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

      【讨论】:

      • 对,非守护进程是有意的,否则执行器调度的任务在程序退出前最多运行一次。我希望终止条件保持不变 - 即。只能通过“从外部杀死”(这首先不是最好的主意,但是...)
      猜你喜欢
      • 2012-09-13
      • 1970-01-01
      • 2023-03-10
      • 1970-01-01
      • 1970-01-01
      • 2022-01-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多