关机钩未终止主线程

时间:2018-07-04 00:00:41

标签: java multithreading server

我正在尝试使用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

如何优雅地解决此问题?

2 个答案:

答案 0 :(得分:1)

  

关闭挂钩完成后,我的主线程也最终没有完成服务器关闭任务

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

这也意味着您的关闭挂钩基本上毫无意义。您可以将其完全删除,并且主线程仍将以相同的方式退出。

“服务器关闭任务”应位于关闭钩子中。那就是它的目的。但是它们不应阻塞或长时间运行:请参阅Javadoc。

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

答案 1 :(得分:0)

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

我认为这是在所有关机钩子线程都返回后(如果接收到信号的情况下),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.