Удаление значений из ConcurrentHashMap правильно


Я создаю простую клиент-серверную программу, которая позволяет пользователям подключаться, изменять свое имя и перемещаться в комнаты для общения. Сервер периодически посылает сигналы сердцебиения каждому клиенту, если они не были активны, и удаляет клиента, если они не отвечают.

Для дальнейшего улучшения очистки сервера я также периодически проверяю, пусты ли мои комнаты, и в этом случае я удаляю комнату с сервера, чтобы предотвратить накопление ненужных данных. Однако это удаление создает проблема. Я использую ConcurrentHashMap, который сопоставляет имя комнаты с ConcurrentHashMap, содержащей имена игроков и их сокеты. Затем, периодически, я обхожу каждую комнату, проверяя, есть ли в ней игроки (размер > 0) или нет. Если нет, я убираю комнату.

Однако это создает очень проблемную ситуацию, когда пользователь выбирает точный момент, чтобы присоединиться к пустой комнате, когда сервер решает очистить ее. Поскольку ConcurrentHashMap обрабатывает всю нижнюю синхронизацию, я не удается синхронизировать эту конкретную ситуацию таким образом, что удаление является 100% потокобезопасным. Пользователь может присоединиться к комнате как раз в тот момент, когда комната удаляется, что приводит к тому, что он застревает в состоянии неопределенности.

Как я могу решить эту проблему?

3 2

3 ответа:

Я бы сохранил внешнюю карту как ConcurrentHashMap,но заменил внутреннюю карту классом чата. Ожидаемый уровень активности в одной комнате, по-видимому, не оправдывает столь сильную параллельную карту.

Класс чата должен быть потокобезопасным, и он должен иметь флаг "закрыто", который указывает, была ли закрыта комната. Метод close() должен использовать блокировку комнаты, чтобы изменить флаг и сделать любые последующие операции незаконными. На самом деле метод close должен возвращать a логическое значение, указывающее, была ли комната закрыта; она должна быть закрыта тогда и только тогда, когда комната была пуста.

Ваш поток проверки холостого хода должен вызывать room.закройте (), а затем удалите его с внешней карты.

Вы можете добавить синхронизированный метод в комнату, что-то вроде boolean closeIfEmpty(). если это удастся, вы можете безопасно удалить комнату с карты (используя метод удаления аргумента 2). если добавляющий код пытается добавить в закрытую комнату, добавление должно завершиться неудачей, и вызывающий объект создаст новую комнату и заменит закрытую комнату.

Вы можете синхронизировать операции удаления и добавления пользователя в комнату на экземпляре Room ConcurrentHashMap, а также сохранить флаг "закрыто" в комнате.

Что-то вроде

void scanRoomAndRemove(Map room) {
 synchronized (room) { 
   // scan room, remove from parent Map if empty
   room.put("closed",new Object());
 }
}

void addPlayerToRoom(Player player,Map room) {
 synchronized(room) {
  if ( !room.containsKey("closed")) {
    // add player to room  
  } else {
    // whine here
  }
 }
}