我似乎有一个不寻常的问题,我无法理解根本原因 .
我正在使用ServerSocket来处理与我正在编写的服务器的连接 . ServerSocket接受它自己的线程中的连接,并且可以通过我设置的isAccepting和isActive变量从主线程控制 .
应该怎么做:服务器启动并接受连接(通过putty) . 我使用命令关闭服务器套接字 . 套接字关闭,线程空闲(我注意到这会导致我捕获的SocketException) . 我使用命令打开一个新的服务器套接字,它再次接受连接 . 我能够连接并可以通过关闭套接字并退出接受连接的循环的命令退出应用程序
怎么了:
服务器启动并接受连接(通过putty) . 我使用命令关闭服务器套接字 . 套接字关闭,线程空闲(我注意到这会导致我捕获的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()正常退出(尽管在关闭期间激活了该过程)
任何见解都将非常感激
1 回答
这堂课里没有任何线程安全的东西 . 几乎每个功能都存在非常基本的问题 .
isAccepting和isActive都需要是volatile或者以同步方式修改才能是线程安全的 . 如果另一个线程正在调用改变这些字段的函数,并且您的run方法已经遍历它们,则可能会得到不可预测的结果 . 试图查看没有内存可见性保证的布尔标志总是一个坏主意 .
setAccepting()具有竞争条件,其中run()线程可能会尝试侦听即将关闭的套接字 .
单例ConnectionMananger实例可以创建多个 . 在您的情况下,您的构造函数什么也不做,但通常不必创建实例更安全 . 使用双重检查锁定来实现这一点,因此只会创建一个实例 .
你的直接问题可能是通过使这两个成员字段都不稳定来“修复”,但就像我说你在这个类中还有太多其他问题那样在多线程环境中使用它是完全安全的 . 此外,捕获异常和简单打印通常是错误的 . 并且您通常希望将runnable子类化并将其传递给线程构造函数,而不是创建线程的子类 .