gpt4 book ai didi

java - 父子线程同步(子线程 "on monitor")

转载 作者:太空宇宙 更新时间:2023-11-04 07:43:06 27 4
gpt4 key购买 nike

我对 Java 总体来说相当陌生,特别是并发编程,所以如果这是一个新手问题,请原谅我。

我有一个线程(服务器),它管理子线程的集合(每个线程代表客户端和服务器之间的 session )。服务器维护 session 集合,当 session 结束时,它会向父服务器发出已完成的信号,以便服务器可以将其从 session 集合中删除。

有人告诉我,如果您打算将 ArrayList 与线程一起使用,则需要对其进行保护,并且除非同步,否则 int 也可能会出现问题,因此使用两者的方法都是同步的。

接下来是服务器和 session 对象的相关部分。

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);
}
}

我遇到的问题出在 session 的 CleanupClient 方法中。当服务器中的 CleanupSession 方法标记为 Synchronized 时, session 线程似乎不会终止。相反,根据 Netbeans 的说法,它们会进入一种名为“On Monitor”的状态。

我试图找出这意味着什么以及如何处理它,但没有帮助。我确实发现监视器就像一个只能被单个线程占用的空间,其他线程必须等待轮到才能使用它,这就是Java中实现并发的方式。然而,我找不到解释,为什么子线程调用父类中的同步方法会触发线程明显永久进入这种状态,或者如何处理它。

我确实发现,如果 Server 类中的 cleanupSession 方法未标记为同步,那么线程会按照我的预期终止。但是,如果我需要同步以维护线程安全,那么我不能只让方法保持不同步并相信运气。

我显然错过了一些基本的东西,但我不确定是什么。如果有人能指出我在这里做错了什么,我将不胜感激。

(附录:我希望我应该使用其他一些 Collection 类,而不是 ArrayList,并且知道它是什么对于解决这种特殊情况肯定会很有帮助,但我也希望获得关于如何在唯一可用选项是同步的一般情况下避免此问题的反馈)

最佳答案

正如 Antimony 已经指出的那样,您会遇到死锁,因为 Server 的两种方法在同一对象(即 Server 实例)上同步,并且 run() 方法永远不会释放锁。

另一方面,您仍然需要某种线程间同步来正确更新 session 列表(如果没有同步,您会遇到两个问题:缺乏更改可见性和数据竞争)。

因此,一种解决方案是仅同步尽可能小的代码部分:对 session 的访问(您不需要在任何地方使用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);
}
}

进行此更改后,您可以通过使用具有内置同步功能的 Map 来完全摆脱 synchronized:ConcurrentHashMap

但是,最好在您掌握了 Java 并发编程的基础知识之后再进行此操作。为此,实践中的 Java 并发是一个很好的起点。我读过的最好的 Java 入门书籍(其中有关于并发性的精彩部分)是 Gosling 和 Holmes 所著的《Java 编程语言》。

关于java - 父子线程同步(子线程 "on monitor"),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15735698/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com