我似乎有一个不寻常的问题,我无法理解其根本原因。
我正在使用 ServerSocket 来处理与我正在编写的服务器的连接。 ServerSocket 在它自己的线程中接受连接,并且可以通过我设置的 isAccepting 和 isActive 变量从主线程进行控制。
应该发生什么: 服务器启动并接受连接(通过腻子)。我使用命令关闭服务器套接字。套接字关闭并且线程空闲(我注意到这会导致我捕获到 SocketException)。我使用命令打开一个新的服务器套接字,它再次接受连接。我能够连接并可以通过关闭套接字并退出循环接受连接的命令退出应用程序
发生了什么:
服务器启动并接受连接(通过腻子)。我使用命令关闭服务器套接字。套接字关闭并且线程空闲(我注意到这会导致我捕获到 SocketException)。我使用命令打开一个新的服务器套接字,这就是线程挂起的地方。它不会打印出代码中的任何调试信息,也不会响应打开/关闭 ServerSocket。使用 Exit 命令会将应用程序卡在退出例程上。有趣的是,如果我在线程代码中的任何位置设置断点,它就会解除并完成,退出。
TL;DR - 关闭套接字会阻塞线程,直到我放置一个断点,之后代码才能正常执行。
尝试导出到可执行 JAR 中,应用程序在退出时挂起,就像在 Eclipse 中一样。
相关部分代码如下:
public class ConnectionManager extends Thread implements IEverfreeManager {
private final int defaultPort = 8002;
private boolean isAccepting = true;
private boolean isActive = true;
private static ConnectionManager instance;
private ServerSocket serverSocket;
private int portNumber = defaultPort;
private Socket workSocket;
public static ConnectionManager instance(){
if (instance == null)
instance = new ConnectionManager();
return instance;
}
public ConnectionManager() {
}
public boolean isAccepting() {
return isAccepting;
}
public void setAccepting(boolean isAccepting) {
this.isAccepting = isAccepting;
try{
if (!isAccepting && !serverSocket.isClosed()){
serverSocket.close();
System.out.println("Closed server on port "+portNumber);
} else{
serverSocket = new ServerSocket(portNumber);
System.out.println("Server on port "+portNumber+" is now accepting connections");
}
}catch(Exception e){
System.out.println("failed to stop accepting");
e.printStackTrace();
}
}
public boolean isActive() {
return isActive || isAlive();
}
public void setActive(boolean isActive) {
this.setAccepting(isActive);
this.isActive = isActive;
}
public int getPortNumber() {
return portNumber;
}
public void setPortNumber(int portNumber) {
this.portNumber = portNumber;
}
private int getNewConnectionId(){
return ++connectionIdCounter;
}
@Override
public void run() {
super.run();
try {
System.out.println("Starting up Connection Manager");
System.out.println("Starting server on port "+portNumber);
serverSocket = new ServerSocket(portNumber);
System.out.println("Server running and ready to accept players");
while (isActive){
if (isAccepting){
try{
System.out.println("Waiting for connection...");
workSocket = serverSocket.accept();
System.out.println("Connected with "+workSocket.getInetAddress());
int id = getNewConnectionId();
} catch (SocketException e){
System.out.println("Notice: "+e.getMessage());
}
}
}
}catch(Exception e) {
e.printStackTrace();
}
}
@Override
public void closeManager() {
setActive(false);
}
使用 setAccepting(false) 然后 setAccepting(true) 不会产生
System.out.println("Waiting for connection...");
消息,直到我在代码中放置断点。
在 setAccepting(false) 之后使用 closeManager() 会产生相同的结果。
仅使用 closeManager() 而不触及 setAccepting() 可以正常退出(尽管在关闭期间激活了该过程)
任何见解将不胜感激
最佳答案
这个类中没有线程安全的东西。几乎每个功能都存在非常基本的问题。
isAccepting 和 isActive 都需要是可变的或以同步方式修改以保证线程安全。如果另一个线程正在调用改变这些字段的函数,并且您的 run 方法已经在它们上循环,您可能会得到不可预知的结果。试图查看没有内存可见性保证的 boolean 标志总是一个坏主意。
setAccepting() 有一个竞争条件,您的 run() 线程可能会尝试监听即将关闭的套接字。
单例 ConnectionMananger 实例可以创建多个。在您的情况下,您的构造函数什么都不做,但不必创建实例通常更安全。使用双重检查锁定来实现这一点,因此只会创建一个实例。
您的直接问题可能会通过使两个 is* 成员字段变为 volatile 来“解决”,但正如我所说,您在此类中仍然有太多其他问题,因此在多线程环境中使用它是完全安全的。另外,捕获Exception 并简单地打印通常是错误的。而且您通常希望子类化 runnable 并将其传递给线程构造函数,而不是创建线程的子类。
关于java - Java中的ServerSocket挂起线程,在放置断点时解锁?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41526242/