【问题标题】:Shutdown hook not terminating main thread关闭钩子不终止主线程
【发布时间】:2018-12-12 08:04:03
【问题描述】:

我正在尝试使用 ctrl-c 键盘快捷键关闭我的服务器,我搜索了一些方法来执行此操作并找到了 this 帖子。

我尝试了这个解决方案,但是当关闭挂钩完成时,我的主线程也没有完成服务器关闭任务,并且进程以代码 1 结束。

这是我的代码:

package test;

import java.io.IOException;
import java.net.ServerSocket;

public class Main implements Runnable {
    private ServerSocket serverSocket;

    private Main() {
        try {
            serverSocket = new ServerSocket(5000);
        } catch (IOException e) {
            e.printStackTrace();
            System.exit(10);
        }

        System.err.println("Waiting ctrl-c ...");

        Runtime.getRuntime().addShutdownHook(new Thread(this));

        try {
            while (serverSocket.accept() != null) {
                doSomething();
            }
            System.err.println("end while");
        } catch (IOException e) {
            System.err.println("end exception");
        } finally {
            System.err.println("trying to close the server...");
            try {
                // simulate a long job
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // this line was never reached
            System.err.println("server closed");
        }

    }

    public static void main(String[] args) {
        new Main();
    }

    private void doSomething() {}

    @Override
    public void run() {
        System.err.println("ctrl-c pressed");
        try {
            System.err.println("trying to close socket");
            serverSocket.close();
            System.err.println("socket closed!!!");
        } catch (IOException e) {
            System.err.println("failed to close socket");
        }
    }
}

这是 IntelliJ IDE 内部的输出:

Waiting ctrl-c ...
ctrl-c pressed
trying to close socket
socket closed!!!
end exception
trying to close the server...

Process finished with exit code 1

我该如何优雅地解决这个问题?

【问题讨论】:

  • 请避免发帖images of text
  • 当你按下 ctrl-c 时,主线程最终进入 finally 块并在那里休眠,同时 jvm 开始关闭,它调用了关闭钩子,但 没有 等待主线程从睡眠中出来。这就是您看不到“服务器已关闭”的原因。

标签: java multithreading server


【解决方案1】:

当关闭钩子完成时,我的主线程也没有完成服务器关闭任务而结束

可能关闭钩子完成之前。这是正确的行为。主线程被JVM强行终止。这就是CTRL/C的效果。

这也意味着你的关机钩子基本上是没有意义的。你可以完全删除它,主线程仍然会以同样的方式退出。

“服务器关闭任务”应该是关闭钩子。这就是它的用途。但它们不应阻塞或长时间运行:请参阅 Javadoc。

NB ServerSocket.accept() 不返回 null。不要编写无意义的测试。

【讨论】:

    【解决方案2】:

    我认为这里发生的情况是关闭挂钩的语义没有很好地记录(在官方文档或博客文章中的任何地方都找不到)。

    我认为发生的情况是,JVM 在所有关闭挂钩线程返回后立即终止(如果收到信号)无需等待主线程完成

    你可以用下面的小程序来模拟这个:

    package cosenmarco;
    
    import java.util.concurrent.CompletableFuture;
    import java.util.concurrent.ExecutionException;
    
    public class Main {
    
        private static volatile CompletableFuture<Void> shutdownFuture = new CompletableFuture<>();
    
        public static void main(String[] args) {
            Runtime.getRuntime().addShutdownHook(new Thread(() -> {
                shutdownFuture.complete(null);
                System.err.println("Shutdown signal");
            }));
    
            try {
                shutdownFuture.get();
                Thread.sleep(1000); // Comment this to have "Finished" correctly printed
                System.err.println("Finished");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
    }
    

    线程也没有中断:我认为线程实际上只是终止了。

    您可能想要做的(我在某处看到过类似的代码,但现在不记得了)是获取对主线程的引用并在关闭挂钩中加入它。像这样的东西对我有用:

    package cosenmarco;
    
    import java.util.concurrent.CompletableFuture;
    import java.util.concurrent.ExecutionException;
    
    public class Main {
    
        private static volatile CompletableFuture<Void> shutdownFuture = new CompletableFuture<>();
    
        public static void main(String[] args) {
            Thread mainThread = Thread.currentThread();
    
            Runtime.getRuntime().addShutdownHook(new Thread(() -> {
                shutdownFuture.complete(null);
                System.err.println("Shutdown signal");
                try {
                    mainThread.join();
                    System.err.println("Joined on main thread.");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }));
    
            try {
                shutdownFuture.get();
                Thread.sleep(1000);
                System.err.println("Finished");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
    }
    

    这会打印(按 ctrl+c 后)以下内容:

    ^CShutdown signal
    Finished
    Joined on main thread.
    

    【讨论】:

    • PS:在我看来,JVM 的信号处理非常糟糕,因为无法指定关闭处理程序的顺序,也无法决定您实际上如何处理信号。我认为常见的做法是安装一个外部信号处理程序并提供一些替代方法来向您的应用程序发出信号关闭(例如,等待连接的侦听套接字会触发正常关闭)编辑:错别字
    • PPS:查看 java.lang.Shutdown#exit 方法源代码后,我更加确信您必须加入所有需要确保在关闭挂钩内完成的线程。
    猜你喜欢
    • 1970-01-01
    • 2019-01-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多