我对Java一般都很陌生,特别是并发编程,所以如果这是一个新手问题,请原谅我。
我有一个线程(服务器),它管理子线程的集合(每个线程代表客户端和服务器之间的会话)。服务器维护会话集合,当会话结束时,它向父服务器发出信号表明它已完成,因此服务器可以从会话集合中删除它。
我被告知如果打算将它们与线程一起使用,则需要保护ArrayLists,并且除非同步,否则int也会有问题,因此使用两者的方法是同步的。
服务器和会话对象的相关部分如下。
public class Server {
private int listenPort = 0;
private ServerSocket serverSocket = null;
private List<Session> sessions = new ArrayList ();
private int lastId = 0;
/**
* Start listening for clients to process
*
* @throws IOException
* @todo Maintain a collection of Clients so we can send global messages
* @todo Provide an escape condition for the loop
*/
synchronized public void run () throws IOException {
Session newSession;
// Client listen loop
while (true) {
//int sessionId = this.Sessions.
newSession = this.initSession (++this.lastId);
this.sessions.add (newSession);
//this.Sessions.add (newSession);
new Thread (newSession).start ();
}
}
/**
*
* @return
* @throws IOException
*/
public Socket accept () throws IOException {
return this.getSocket().accept ();
}
/**
*
* @param closedSession
*/
synchronized public void cleanupSession (Session closedSession) {
this.sessions.remove (closedSession);
}
}
这是Session类:
public class Session implements Runnable {
private Socket clientSocket = null;
private Server server = null;
private int sessionId = 0;
/**
* Run the session input/output loop
*/
@Override
public void run () {
CharSequence inputBuffer, outputBuffer;
BufferedReader inReader;
try {
this.sendMessageToClient ("Hello, you are client " + this.sessionId);
inReader = new BufferedReader (new InputStreamReader (this.clientSocket.getInputStream (), "UTF8"));
do {
// Parse whatever was in the input buffer
inputBuffer = this.requestParser.parseRequest (inReader);
System.out.println ("Input message was: " + inputBuffer);
// Generate a response for the input
outputBuffer = this.responder.respond (inputBuffer);
System.out.println ("Output message will be: " + outputBuffer);
// Output to client
this.sendMessageToClient (outputBuffer.toString ());
} while (!"QUIT".equals (inputBuffer.toString ()));
} catch (IOException e) {
Logger.getLogger (Session.class.getName ()).log (Level.SEVERE, null, e);
} finally {
this.cleanupClient ();
}
}
/**
* Terminate the client connection
*/
public void cleanupClient () {
try {
this.streamWriter = null;
this.clientSocket.close ();
this.server.cleanupSession (this);
} catch (IOException e) {
Logger.getLogger (Session.class.getName ()).log (Level.SEVERE, null, e);
}
}
/**
*
* @param clientSocket
*/
public Session (Server owner, int sessionId) throws IOException {
System.out.println ("Class " + this.getClass () + " created");
this.server = owner;
this.sessionId = sessionId;
this.clientSocket = this.server.accept ();
System.out.println ("Session ID is " + this.sessionId);
}
}
我遇到的问题是在会话的CleanupClient方法中。当服务器中的CleanupSession方法标记为已同步时,会话线程似乎不会终止。根据Netbeans的说法,他们进入一个名为“On Monitor”的州。
我试图找出这意味着什么以及如何应对它并没有帮助。我确实发现监视器就像一个只能由单个线程占用的空间,其他线程必须等待轮到他们使用它,这就是Java中实现并发的方式。但是,我无法找到解释为什么在父类中调用同步方法的子线程会触发线程显然永久地进入此状态,或者该如何处理它。
我确实发现如果Server类中的cleanupSession方法没有标记为synchronized,那么线程就像我预期的那样终止。但是,如果我需要同步以保持线程安全,那么我不能让方法不同步并相信运气。
我显然遗漏了一些基本的东西,但我不确定是什么。如果有人能指出我在这里做错了什么,我会很感激。
(附录:我希望我应该使用的是其他一些类而不是ArrayList,并且知道它是什么肯定会很好地解决这个特殊情况,但我也想要反馈如何避免这个在一般情况下,唯一可用的选项是同步的问题)
答案 0 :(得分:1)
正如Antimony已经指出的那样,你得到死锁,因为两个Server同步对象的方法(即Server
实例)和run()
方法永远不会释放锁。
另一方面,你仍然需要某种线程间同步来正确更新sessions
列表(没有同步,你会遇到两个问题:缺乏可见性变化和数据竞争)。
因此,一种解决方案是仅同步代码中最小的可能部分:对sessions
的非常访问(您不需要在任何地方使用this.
,只有在本地名称影响实例名称的地方变量):
...
public void run () throws IOException {
Session newSession;
// Client listen loop
while (true) {
...
newSession = initSession (++lastId);
synchronized (this) {
sessions.add (newSession);
}
...
}
}
public void cleanupSession (Session closedSession) {
synchronized (this) {
sessions.remove (closedSession);
}
}
你认为List
不是最合适的,你需要HashMap
,因为你要做的就是添加新的客户端并搜索客户端,并命令存储客户端收集并不重要(即使它很重要,最好使用一些有序的Map
,如TreeMap
,以提高性能。因此,您可以将Server
代码更改为:
private Map<Integer, Session> sessions = new HashMap<IntegerPatternConverter, Session>();
...
// Client listen loop
while (true) {
int key = ++lastId;
newSession = initSession (key);
synchronized (this) {
sessions.put (key, newSession);
}
new Thread (newSession).start ();
}
...
public void cleanupSession (int closedSessionKey) {
synchronized (this) {
sessions.remove (closedSessionKey);
}
}
完成此更改后,您可以使用内置同步的synchronized
完全摆脱Map
:ConcurrentHashMap
。
但是,在获得Java并发编程的基础知识后,这样做会更好。为此, Java Concurrency in Practice 是一个很好的起点。我读过的最好的入门Java书(有很好的兼容性)是Gosling和Holmes的 Java Programming Language 。
答案 1 :(得分:0)
问题是你遇到了僵局。
您的Server.run
方法永久保留在Server
监视器上。由于cleanupSession
也尝试进入此监视器,因此只要服务器正在运行,每次尝试从其他线程调用它都会死锁。
无论如何,sychronized并不是你想要的。我建议您查看java.util.Concurrency
。