Удаление значений из ConcurrentHashMap правильно
Я создаю простую клиент-серверную программу, которая позволяет пользователям подключаться, изменять свое имя и перемещаться в комнаты для общения. Сервер периодически посылает сигналы сердцебиения каждому клиенту, если они не были активны, и удаляет клиента, если они не отвечают.
Для дальнейшего улучшения очистки сервера я также периодически проверяю, пусты ли мои комнаты, и в этом случае я удаляю комнату с сервера, чтобы предотвратить накопление ненужных данных. Однако это удаление создает проблема. Я использую ConcurrentHashMap, который сопоставляет имя комнаты с ConcurrentHashMap, содержащей имена игроков и их сокеты. Затем, периодически, я обхожу каждую комнату, проверяя, есть ли в ней игроки (размер > 0) или нет. Если нет, я убираю комнату.
Однако это создает очень проблемную ситуацию, когда пользователь выбирает точный момент, чтобы присоединиться к пустой комнате, когда сервер решает очистить ее. Поскольку ConcurrentHashMap обрабатывает всю нижнюю синхронизацию, я не удается синхронизировать эту конкретную ситуацию таким образом, что удаление является 100% потокобезопасным. Пользователь может присоединиться к комнате как раз в тот момент, когда комната удаляется, что приводит к тому, что он застревает в состоянии неопределенности.
Как я могу решить эту проблему?
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 } } }