Проблема параллелизма Hashmap
У меня есть Hashmap, который по соображениям скорости я бы не хотел блокировать. Будет ли обновление и доступ к нему одновременно причиной проблем, если я не возражаю против устаревших данных?
Мой доступ получает, не перебирает его, а удаления являются частью обновлений.
9 ответов
Да, это вызовет серьезные проблемы. Одним из примеров является то, что может произойти при добавлении значения в хэш-карту: это может вызвать перефразирование таблицы, и если это происходит, когда другой поток выполняет итерацию по списку столкновений (хэш-таблице "bucket"), этот поток может ошибочно не удается найти ключ, который существует на карте. HashMap
явно небезопасно для одновременного использования.
использование ConcurrentHashMap
вместо.
Важность синхронизации или использования ConcurrentHashMap не может быть преуменьшена.
Вплоть до того, как пару лет назад у меня было ложное впечатление, что я могу обойтись только синхронизацией операций put и remove в HashMap. Это, конечно, очень опасно и фактически приводит к бесконечному циклу в HashMap.get() на некоторых (я думаю, ранних 1.5) jdk.
То, что я сделал пару лет назад (и на самом деле не должно быть сделано):
public MyCache {
private Map<String,Object> map = new HashMap<String,Object>();
public synchronzied put(String key, Object value){
map.put(key,value);
}
public Object get(String key){
// can cause in an infinite loop in some JDKs!!
return map.get(key);
}
}
РЕДАКТИРОВАТЬ: думал, что я бы добавил пример того, что не следует делать (см. Выше)
Если вы сомневаетесь, проверьте Javadocs класса:
Обратите внимание, что эта реализация не синхронизирована. Если несколько потоков обращаются к хэш-карте одновременно, и хотя бы один из потоков структурно изменяет карту, она должна быть синхронизирована извне. (Структурная модификация - это любая операция, которая добавляет или удаляет одно или несколько сопоставлений; простое изменение значения, связанного с ключом, который уже содержится в экземпляре, не является структурной модификацией.) Обычно это выполняется путем синхронизации с некоторым объектом, который естественным образом инкапсулирует карту, Если такого объекта не существует, карту следует "обернуть" с помощью метода Collections.synchronizedMap. Это лучше всего делать во время создания, чтобы предотвратить случайный несинхронизированный доступ к карте:
Map m = Collections.synchronizedMap(new HashMap(...));
(акцент не мой)
Исходя из того, что вы сказали, что ваши потоки будут удалять сопоставления с карты, ответ таков: да, это определенно вызовет проблему, и да, это определенно небезопасно.
Да. Очень плохие вещи произойдут. Например, ваш поток может застрять в бесконечном цикле.
Либо используйте ConcurrentHashMap, либо NonBlockingHashMap
Условия, которые вы описываете, не будут удовлетворены HashMap
, Поскольку процесс обновления карты не является атомарным, вы можете встретить карту в недопустимом состоянии. Многократные записи могут оставить это в поврежденном состоянии. ConcurrentHashMap (1.5 или более поздняя версия) делает то, что вы хотите.
Если под "одновременно" вы подразумеваете несколько потоков, то да, вам нужно заблокировать доступ к нему (или использовать ConcurrentHashMap или аналогичный, который делает блокировку за вас).
Нет, проблем не будет, если вы сделаете следующее:
Поместите ваши данные в HashMap при первой загрузке одного потока, прежде чем произойдет какая-либо многопоточность. Это связано с тем, что процесс добавления данных изменяет значение modcount и отличается при первом добавлении (возвращается ноль) и замене данных (старые данные будут возвращены, но modcount не будет изменен). Modcount - это то, что делает итераторы отказоустойчивыми. Если вы используете get, то ничего не будет повторяться, так что все в порядке.
Используйте одинаковые ключи во всем приложении. Как только приложение запускается и загружает свои данные, никакие другие ключи не могут быть назначены этой карте. Таким образом, get либо получит устаревшие данные, либо данные, которые были вставлены свежими - проблем не будет.
Как и другие упомянутые, используйте ConcurrentHashMap или синхронизируйте карту при обновлении.
Я читал здесь или в другом месте, нет, вы не получаете доступ из многопоточности, но никто не говорит, что на самом деле происходит.
Итак, я видел сегодня (вот почему я отвечаю на этот старый вопрос) в приложении, работающем в производстве с марта: 2, поставленные на тот же HashSet (тогда HashMap), вызывают перегрузку процессора (около 100%) и увеличение памяти 3 ГБ, затем вниз GC. Мы должны перезапустить приложение.