1

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

这是会话类:

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 方法没有标记为同步,那么线程会按照我的预期终止。但是,如果我需要同步以维护线程安全,那么我不能只让方法不同步并相信运气。

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

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

4

2 回答 2

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在此更改之后,您可以通过使用Mapwith 内置同步完全摆脱: ConcurrentHashMap.

但是,最好在您掌握 Java 并发编程的基础知识之后再执行此操作。为此,Java Concurrency in Practice是一个很好的起点。我读过的最好的 Java 入门书籍(其中有一篇关于并发的精彩部分)是Gosling 和 Holmes 合着的 The Java Programming Language

于 2013-04-01T00:24:22.343 回答
0

问题是你已经陷入僵局。

您的Server.run方法永久保留在Server监视器上。由于cleanupSession还尝试进入此监视器,只要服务器正在运行,每次尝试从不同的线程调用它都会死锁。

无论如何,同步不会做你想做的事。我建议调查一下java.util.Concurrency

于 2013-03-31T23:34:40.580 回答