【问题标题】:How does the JVM terminate daemon threads? or How to write daemon threads that terminate gracefullyJVM 如何终止守护线程?或如何编写优雅终止的守护线程
【发布时间】:2012-01-29 14:10:54
【问题描述】:

假设场景:
我有一个守护线程负责一些 I/O,主线程完成并返回,JVM 决定终止我的守护线程。

它是如何做到的?打断?敲定?如何编码我的守护线程,以便它在终止时做出优雅的反应?

【问题讨论】:

  • 你看源码了吗?
  • @StephenC 我没有想到这一点,并且肯定会产生一个明确的答案(尽管不是必然有用的)。然而,我个人并没有足够的勇气去尝试,也不希望其他人这样做。
  • 好吧,让我更明确一点。回答此类问题的最佳方法是查看源代码,或者至少查看 Java 代码。一般来说,它很容易阅读并且评论很好。 (而且我不明白一个问题的明确答案如何不如一个非明确的答案有用......尤其是如果 you 是阅读代码的人!)
  • 告诉 OP 查看源代码是没有答案的。这显示了特定的实现,而不是 API 要求。文档有它的位置。

标签: java multithreading jvm daemon


【解决方案1】:

我只是写了以下代码作为测试:

public class DaemonThreadPlay {
    public static void main(String [] args) {
        Thread daemonThread = new Thread() {
            public void run() {
                while (true) {
                    try {
                        System.out.println("Try block executed");
                        Thread.sleep(1000l);
                    } catch (Throwable t) {
                        t.printStackTrace();
                    }
                }
            }

            @Override
            public void finalize() {
                System.out.println("Finalize method called");
            }
        };
        daemonThread.setDaemon(true);
        daemonThread.start();

        try {
            Thread.sleep(2500l);
        } catch (Throwable t) {
            //NO-OP
        }
    }
}    

我在守护线程的 catch 块和 finalize 方法中设置了断点。即使执行了 try 块,也没有到达断点。显然这段代码存在同步/计时问题,但我认为我们可以安全地得出结论,守护线程不会在关闭时中断,也不一定会调用它们的 finalize() 方法。

您始终可以向 JVM 运行时添加关闭挂钩:

Thread shutdownHook = ... // construct thread that somehow
                          // knows about all the daemon threads
Runtime.getRuntime().addShutdownHook(shutdownHook);

您的关闭挂钩显然可以执行“优雅”关闭所需的任何任务。

【讨论】:

  • 我喜欢关机钩子。
  • 在寻找获取活动线程的方法时发现 this。必须遍历数组以检查至少一个用户线程。
    我认为关闭钩子是最好的解决方案,以前从不知道这个功能。我想我会在我的 Main 类中保留对我的关闭挂钩的引用,并将我的守护程序线程的集合保存在关闭挂钩中。然后我就可以打断他们了。一旦我实现它,我会尝试发布我的代码。
  • 如果你显式调用System.gc()并且没有设置JVM选项-XX:+DisableExplicitGC,那么守护线程将会退出。这意味着垃圾收集器负责在所有用户线程完成后关闭守护线程。
  • 您可以做的另一件事是引入 finally 块,无论在 try-catch 块中发生什么,都可以保证调用它!通过这种方式,您可以关闭 Daemon 线程打开的任何资源并在其中实现任何优雅终止!
  • @skiller3 小心不要妄下结论。在可视化调试期间停止 JVM 时,很可能根本不会命中断点,因为 JVM 已经与调试器断开连接(我用 IntelliJ IDEA 见证了这种行为)。
【解决方案2】:

我认为你误解了守护线程是什么。

what is a daemon thread in java

总而言之,它基本上意味着守护线程不应该进行任何 I/O 或持有任何资源。如果您违反了这个基本规则,那么您的线程不符合成为守护线程的条件。

添加关闭挂钩是确保您的代码在 JVM 终止之前被调用的标准方法,但即使这样也不能 100% 保证 - 例如,您的 JVM 可能会崩溃,让操作系统以一种方式整理资源保护操作系统,但很可能使您的应用程序处于不一致/错误状态。

系统检查点和恢复机制可以追溯到软件的早期(例如操作系统和批处理操作),不幸的是,这个*不断被重新发明,因为没有解决这个问题的“银弹”方法 (API)以足够通用的方式解决问题。

【讨论】:

  • Daemon 线程不应该做 IO 或持有资源的说法的依据是什么?
  • 点击提供的链接,您将获得一些见解。
  • 对不起,我不同意。只要知道守护线程可以在处理过程中被杀死,就没有危险,除非你知道一些我不知道的关于 java 资源泄漏的事情。
  • 如果守护线程拥有数百个资源,那么终止 JVM 是很疯狂的,更糟糕​​的是,当它处于一系列关键写入操作的中间时会发生关闭。您实际上是在冒着应用程序一致性问题的风险。您也不同意撰写“实践中的 Java 并发”的 Brian Goetz……不同意也没关系,但我认为在极少数情况下您可能是正确的。
【解决方案3】:

AFAIK,守护线程并不是真正用于主流 I/O 工作。如果所有线程都完成,JVM 可能会突然关闭所有守护线程。根据您的要求,可能的解决方法是创建一个 ExecutorService,如下所示:

ExecutorService execPool = Executors.newSingleThreadExecutor(new ThreadFactory() {

    @Override    
    public Thread newThread(Runnable runnable) {       
         Thread thread = Executors.defaultThreadFactory().newThread(runnable);
         thread.setDaemon(true);
         return thread;    
    } 
}); 

从 Shutdown hook 调用 executorservice 关闭方法。

Runtime.getRuntime().addShutdownHook(....)

【讨论】:

  • 您的 newThread 方法确实每次都会创建一个新的线程工厂。对Executors.defaultThreadFactory() 的调用只返回一个new DefaultThreadFactory()
【解决方案4】:

使用中断和加入:

class Daemon extends Thread {
    public void run() {
        while (true) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                System.out.println(e);
                break;
            }
        }
        System.out.println("exit run()");
    }
}   
public class So8663107 {
    public static void main(String[] arguments) throws InterruptedException {
        System.out.println(Thread.activeCount());
        Daemon daemon = new Daemon();
        daemon.setDaemon(true);
        daemon.start();
        Thread.sleep(2500);
        System.out.println(Thread.activeCount());
        daemon.interrupt();
        daemon.join();
        System.out.println(Thread.activeCount());
    }
}

【讨论】:

  • 我认为您没有理解这个问题。我不想中断我的守护线程。我想知道JVM如何终止它们,以及如何处理它。
  • 抱歉,我试图回答:“我怎样才能对我的守护线程进行编码以便它优雅地终止?”