【问题标题】:Socket client does not terminate套接字客户端不终止
【发布时间】:2016-02-12 00:32:55
【问题描述】:

为什么客户端代码没有终止? (在运行客户端之前,我们需要先启动服务端程序)

服务器代码:

public class ServerSocketTest {

    static final int PORT = 9999;

    public static void main(String[] args) throws IOException {
        final ServerSocket serverSocket = new ServerSocket(PORT);

        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                try {
                    serverSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });

        while (true) {
            final Socket socket = serverSocket.accept();
            Runnable runnable = new Runnable() {

                @Override
                public void run() {
                    System.out.println(socket);
                }
            };
            Thread thread = new Thread(runnable);
            thread.start();
        }

    }

}

客户端代码:

public class SocketClient {

    public static void main(String[] args) throws UnknownHostException,
            IOException {
        final Socket socket = new Socket("localhost", ServerSocketTest.PORT);

        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                System.out.println("waiting to close socket " );
                printThread(Thread.currentThread());
                try {                   
                    socket.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });

        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                System.out.println("waiting to exit");
                printThread(Thread.currentThread());                
                System.exit(0);
            }
        });
    }

    static void printThread(Thread currentThread) {
        System.out.println(currentThread + ": Alive=" + currentThread.isAlive()
                + ",Daemon=" + currentThread.isDaemon());
    }
}

我使用 Ubuntu。使用 jconsole,我可以跟踪以下线程以及其他一些 RMI 和 JMX 线程:


名称:引用处理程序 状态:在 java.lang.ref.Reference$Lock@14f38ff 上等待 总被阻止:1 总等待:2

堆栈跟踪:

java.lang.Object.wait(Native Method)
java.lang.Object.wait(Object.java:503)
java.lang.ref.Reference$ReferenceHandler.run(Reference.java:133)

名称:终结者 状态:在 java.lang.ref.ReferenceQueue$Lock@3037a0 上等待 总被阻止:1 总等待:2

堆栈跟踪:

java.lang.Object.wait(Native Method)
java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:135)
java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:151)
java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:177)

名称:信号调度员 状态:可运行 阻止总数:0 等待总数:0

堆栈跟踪:


名称:DestroyJavaVM 状态:等待 net.SocketClient$2@39b27b 阻止总数:0 等待总数:1

堆栈跟踪:

java.lang.Object.wait(Native Method)
java.lang.Thread.join(Thread.java:1258)
java.lang.Thread.join(Thread.java:1332)
java.lang.ApplicationShutdownHooks.runHooks(ApplicationShutdownHooks.java:106)
java.lang.ApplicationShutdownHooks$1.run(ApplicationShutdownHooks.java:46)
java.lang.Shutdown.runHooks(Shutdown.java:123)
java.lang.Shutdown.sequence(Shutdown.java:167)
java.lang.Shutdown.shutdown(Shutdown.java:234)
- locked java.lang.Class@127bd04

名称:Thread-1(这是我为调用 System.exit 添加的关闭挂钩) 状态:在 java.lang.Class@127bd04 上被阻塞 拥有者:DestroyJavaVM 阻止总数:1 等待总数:0

堆栈跟踪:

java.lang.Shutdown.exit(Shutdown.java:212)
java.lang.Runtime.exit(Runtime.java:107)
java.lang.System.exit(System.java:960)
net.SocketClient$2.run(SocketClient.java:31)

名称:SIGINT 处理程序(这似乎是在按下 Control+C 时生成的) 状态:在 java.lang.Class@127bd04 上被阻塞 拥有者:DestroyJavaVM 阻止总数:1 等待总数:0

堆栈跟踪:

java.lang.Shutdown.exit(Shutdown.java:212)
java.lang.Terminator$1.handle(Terminator.java:52)
sun.misc.Signal$1.run(Signal.java:212)
java.lang.Thread.run(Thread.java:722)

【问题讨论】:

  • “客户端代码不会终止”是什么意思?看来您的客户端甚至没有建立连接。
  • 似乎从任何关闭挂钩调用 System.exit(status) 都会导致该线程和 DestroyJavaVM 线程之间的死锁。同样在 SIGINT 处理程序(如果按下 Control+C)线程和 DestroyJavaVM 线程之间。当然,在已经启动关机时调用 System.exit(status) 几乎没有意义。
  • 嗯...非常有趣。你能打开你的任务管理器看看你的客户端产生了多少线程吗?

标签: java


【解决方案1】:

它不会停止,因为您正在运行实时线程(您的钩子)。还是您的意思是即使向java进程发送了SIGTERM或SIGINT信号也无法停止?

【讨论】:

  • 为什么钩子不会终止,因为它们没有分配任何非终止任务。即使是 Ctrl+C(SIGINT) 也无法阻止它。我需要发出 kill -9
【解决方案2】:

我不确定我在这里看到使用addShutdownHook 的意义。来自文档:

关闭挂钩也应该快速完成它们的工作。当程序调用 exit 时,期望虚拟机将立即关闭并退出。当虚拟机由于用户注销或系统关闭而终止时,底层操作系统可能只允许关闭和退出的固定时间量。因此,不建议尝试任何用户交互或在关闭挂钩中执行长时间运行的计算。

当然,终止时不需要socket.close(),因为如果您的代码没有机会关闭它,操作系统会自动为您执行此操作。我会尝试在不使用关闭挂钩的情况下重新编写您的代码。

【讨论】:

  • 操作系统自动释放的资源(如socket)有哪些?
  • 打开的文件、窗口句柄、套接字、管道(当所有使用管道的进程都终止时)等
  • 感谢您的回复。一定要知道一些站点,其中记录了此类资源的详尽列表(以及例外情况)。所有这些资源都必须是本地的,对吗?操作系统能否跟踪某个进程获取的网络资源(如远程数据库、MQ等)?
  • @labbhattacharjee:操作系统不需要跟踪任何远程资源。您使用套接字与远程服务器进行通信,当您的进程关闭时,操作系统会为您关闭套接字(如果您还没有关闭)。服务器(DB、MQ 等)将收到套接字已关闭的通知,并根据您的客户端离开清理自己的世界。
【解决方案3】:

您不需要在关闭挂钩中调用 System.exit(),而且您绝对不应该这样做,因为它可能会产生死锁或无限递归。你也不需要其他的。最好让你的线程成为守护线程。

【讨论】:

  • 我已经达到了这一点:“在已经启动关机时调用 System.exit(status) 几乎没有意义。”。但是如果发生死锁,共享资源是什么?你能肯定地告诉我或分享任何参考吗?您是否察觉到将关闭挂钩作为守护进程的任何行为变化?
  • @labbhattacharjee 很抱歉让您失望了,但这不是调试服务,我还没有执行您的代码。而且我并不是建议让您的关闭挂钩守护程序线程:它们是可运行的,因此使它们成为守护程序的效果为零。我建议完全摆脱这些多余的关闭挂钩,并让剩余的线程成为守护进程。
猜你喜欢
  • 2016-10-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-01-31
  • 2019-04-29
  • 1970-01-01
  • 2015-02-08
  • 2012-02-19
相关资源
最近更新 更多