2013-03-31 2 views
1

Я довольно новичок в Java в целом и в параллельном программировании, в частности, так что простите меня, если это вопрос новичков.Синхронизация между родительским и дочерним потоком (дочерний поток «на мониторе»)

У меня есть поток (сервер), который управляет коллекцией дочерних потоков (каждый поток представляет собой сеанс между клиентом и сервером). Сервер поддерживает набор сеансов, и когда сеанс заканчивается, он сигнализирует о завершении сеанса родительского сервера, чтобы сервер мог удалить его из коллекции сеансов.

Мне сказали, что ArrayLists необходимо защищать, если вы намерены использовать их с потоками, а ints также могут иметь проблемы, если не синхронизированы, поэтому методы, которые используют оба, синхронизируются.

Далее следуют соответствующие части сервера и объекты сеанса.

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. Однако я не мог найти объяснения, почему дочерний поток, вызывающий синхронизированный метод в родительском классе, вызовет поток, видимо, постоянно находящийся в этом состоянии или что с ним делать.

Я обнаружил, что если метод cleanupSession в классе Server не помечен как синхронизированный, то потоки прекращаются, как я ожидал от них. Однако, если мне нужно синхронизировать, чтобы поддерживать безопасность потоков, я не могу просто оставить метод несинхронизированным и доверять удаче.

У меня явно нет чего-то основного, но я не уверен, что. Если кто-нибудь может указать, что я здесь делаю, я буду признателен.

(Добавление: Я ожидаю, что есть другой класс Collection, который я должен использовать, а не ArrayList, и, зная, что это будет, несомненно, будет здорово в решении этого конкретного случая, но мне также хотелось бы получить обратную связь о том, как избежать эта проблема в общем случае, когда единственной доступной опцией является синхронизация)

ответ

1

Как уже указывалось в Сурьме, вы получаете блокировку мертвой точки, поскольку оба метода синхронизации Сервера на одном и том же объекте (а именно, 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 на практике - хорошее место для начала. Лучшая вступительная Java-книга, которую я прочитал (с приятной частью о параллелизме), - Язык программирования Java Гослинга и Холмса.

+0

Спасибо. Я изучил несколько модулей Java во время учебы, но это первый материал, который я сделал с параллелизмом. Ясно, что мне еще многое предстоит узнать. – GordonM

+0

Кроме того, как я думаю, я прочитал в «Чистом коде», ints необходимо синхронизировать или заменить AtomicInteger, поэтому я переключил код на использование AtomicInteger. – GordonM

+0

@ GordonM в вашем коде есть только три 'ints': на сервере,' listenPort' и 'lastId' доступны только из одного потока (тот, который выполняет метод Server.run()'), t необходимо синхронизировать. То же самое относится к единственному «int» в клиенте. Вам нужна синхронизация только тогда, когда к некоторым данным можно получить доступ более чем к одному потоку в течение его жизни. –

0

Проблема в том, что вы зашли в тупик.

Ваш Server.run метод постоянно держится на мониторе Server. Так как cleanupSession также пытается войти в этот монитор, каждая попытка вызвать его из другого потока будет заторможен до тех пор, пока сервер будет запущен.

В любом случае, синхронизированный не делает то, что вы хотите. Я бы рекомендовал посмотреть на java.util.Concurrency.