JVM如何终止守护线程?或者如何编写能够优雅终止的守护线程?

34
我有一个负责I/O操作的守护线程,主线程完成并返回后,JVM决定终止我的守护线程。 它是如何做到这一点的?是中断吗?还是终结?我该如何编写守护线程以便在被终止时能够优雅地反应?

1
你看过源代码了吗? - Stephen C
2
@StephenC 这个想法我之前没有考虑过,这确实会得出一个明确的答案(尽管不一定是有用的)。然而,就我个人而言,我并不勇敢去尝试,也不指望其他人会这么做。 - Aaron J Lang
好的,让我更明确一些。回答这样的问题最好的方法是查看源代码或至少Java代码。一般来说,它易于阅读且注释良好。(我无法理解为什么对一个问题给出明确的答案比非明确的答案更不实用...特别是如果是阅读代码的人!) - Stephen C
16
告诉提问者查看源代码并不是答案。那只展示了特定的实现方式,而非API需求。文档有其存在的意义。 - Raedwald
4个回答

13

我刚用以下代码作为测试:

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

我在守护线程(daemon thread)的catch块和finalize方法中设置了断点,尽管try块已被执行,但两个断点都没有被触发。显然,这段代码存在同步/时间问题,但我认为我们可以得出结论:在关闭时不会中断守护线程,它们的finalize()方法也不一定被调用。

您可以始终向JVM Runtime添加一个shutdown hook:

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

你的关闭挂钩可以执行所有必要的任务,以实现“优雅”的关闭。


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

7
我认为你误解了守护线程的含义。
请参考Java中的守护线程是什么。简而言之,它基本上意味着一个守护线程不应该执行任何I/O或持有任何资源。如果您违反了这个基本规则,则您的线程就不符合守护线程的条件。
添加关闭挂钩是确保在JVM终止之前调用代码的标准方法,但即使这样也不能100%地保证 - 例如,您的JVM可能会崩溃,留下操作系统以一种保护操作系统但很可能使您的应用程序处于不一致/错误状态的方式来整理资源。
系统检查点和恢复机制可以追溯到软件的早期(例如,操作系统和批处理操作),不幸的是,由于没有能够以足够通用的方式解决此问题的“银弹”方法(API),因此这个问题一直在被重新发明。

宣称守护线程不应该执行IO操作或持有资源的依据是什么? - ykaganovich
点击提供的链接,您将获得一些见解。 - KRK Owner
2
抱歉,我不同意。只要明白守护线程可以在处理过程中被终止,就没有危险,除非你知道一些我不知道的关于Java资源泄漏的事情。 - ykaganovich
2
如果守护线程持有数百个资源,甚至更糟的是在关键写操作序列的中途发生关闭,那么终止JVM是不明智的。这实际上会冒着应用程序一致性风险。您还与编写《Java并发实践》的Brian Goetz意见不合...意见不合无可厚非,但我认为您正确的情况非常少。 - KRK Owner

4
据我所知,守护线程并不适合主流的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;    
    } 
}); 

在关闭钩子中调用ExecutorService的shutdown方法。

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

你的 newThread 方法确实每次都创建一个新的线程工厂。调用 Executors.defaultThreadFactory() 只会返回一个 new DefaultThreadFactory() - benez

1

使用中断和加入:

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

3
我认为你没有理解这个问题。我不想打断我的守护线程。我想知道JVM如何终止它们,以及如何处理它。 - Aaron J Lang
抱歉,我正在尝试回答:“如何编写守护线程以使其正常终止?” - Ray Tayek

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接