Как использовать ConcurrentHashMap в Java
До Java 1.5, если вам нужна была реализация Map, которую можно безопасно использовать в многопоточной Java-программе, у вас были только Hashtable или synchronized Map, потому что HashMap НЕ безопасен.
Оглавление
- Введение
- Как реализован ConcurrentHashMap в Java
- Некоторые важные свойства ConcurrentHashMap
- Когда следует использовать ConcurrentHashMap в Java
Введение
ConcurrentHashMap был представлен как альтернатива Hashtable в Java 1.5 как часть пакета многопоточности. C ConcurrentHashMap у вас есть лучший выбор, не только потому, что это безопасно в многопоточном окружении, но так же предоставляет лучшую производительность по сравнению с Hashtable и synchronizedMap. ConcurrentHashMap работает производительнее, потому что блокирует лишь часть Map. Позволяет одновременные операции чтения и в тоже время обеспечивает целостность, синхронизируя операции записи.
Как реализован ConcurrentHashMap в Java
ConcurrentHashMap был разработан как альтернатива Hashtable и обеспечивает всю функциональность, предоставляемой Hashtable, с дополнительными возможностями, называемые уровень одновременности (concurrency level). ConcurrentHashMap позволяет множеству читателей одновременное чтение без использования блокировок. Это достигается разделением Map на различные части, основываясь на «уровне одновременности» и блокированием только части Map при обновлении. По умолчанию, уровень одновременности равен 16, и соответственно Map разделяется на 16 частей и каждая часть управляется отдельной блокировкой. Это означает, что 16 потоков могут работать с Map одновременно, пока они работают с разными частями Map. Это делает ConcurrentHashMap высокопроизводительным, в тоже время не ухудшая потоко-безопасность.
Некоторые важные свойства ConcurrentHashMap
Но существует особенности. Поскольку операции обновления не синхронизирующие, одновременная выборка данных может не показать недавние изменения.
Так же стоит помнить об особенностях итерации по Map. Итератор, возвращаемый keySet
, несогласованный и отражает состояние ConcurrentHashMap на определенный момент и может не отражать недавние изменения. Он так же является fail-safe и не выбрасывает исключение ConcurrentModificationException
.
Уровень параллельности по умолчанию равен 16 и может быть изменен во время создания ConcurrentHashMap. Поскольку данный уровень используется «под капотом» и указывает на число одновременных обновлений без конкуренции, в случае наличия лишь нескольких обновляющих потоков, имеет смысл использовать небольшое значение.
ConcurrentHashMap не позволяет хранить null
.
ConcurrentHashMap предоставляет метод putifAbsent(key, value)
, который позволяет добавить в Map ключ-значение, в случае отсутствия данной записи, причем сделать это атомарно, недопустив состояния гонки. Известный подход проверить-на-отсутствие-и-вставить (get, put) для ConcurrentHashMap не работает, поскольку во время операции вставки put
вся Map не заблокирована, и пока один поток добавляет значение, вызов get
в другом потоке все еще может вернуть null
, и как следствие — один поток может переписать значение, добавленное другим потоком. Конечно, вы можете обернуть код синхронизирующим блоком, сделав его потоко-безопасным, но это лишь сделает ваш код одно-поточным.
Когда следует использовать ConcurrentHashMap в Java
ConcurrentHashMap отлично подходит, когда у вас множество читающих потоков и несколько пишущих. Если число пишущих превосходит или даже равно числу читающих, то производительность ConcurrentHashMap уменьшается до уровня synchronizedMap и Hashtable. Производительность ConcurrentHashMap падает, поскольку происходит блокировка всей Map, и каждый читающий поток ждет пишущего, который работает с этой областью Map.
ConcurrentHashMap хороший выбор для кэшей, которые могут быть инициализированы во время старта приложения и позже предоставлять доступ запрашивающим потокам.
ConcurrentHashMap хороший заменитель Hashtable и может быть использован повсеместно, однако следует помнить об особенностях синхронизации у ConcurrentHashMap по сравнению с Hashtable.
Изменения в Java 8
Внимание!
В Java 8 произошли изменения в ConcurrentHashMap. Статья несколько устаревшая в части сегментирования хэштаблицы на основе заданного уровня параллелизма.
Версии на других языках
- English version — How to use ConcurrentHashMap in Java
Похожие статьи
Комментарии
Если у вас есть вопросы или комментарии — буду рад их услышать — присоединяйтесь к беседе!
Alex Anfield Октябрь 2019
Очень полезная статья - в ней обсуждаются вещи, которые я должен был знать, но не знал. Спасибо!!!
Gor Reaper Октябрь 2019
А есть статьи про equals и hashcode? чёто не могу найти...
Олег Полторацкий Октябрь 2019
Эта тема настолько сильно заезженная, так что не стал писать)
Олег Полторацкий Октябрь 2019
Хотя, если есть спрос, то может и стоит. Смотрю по гугл-аналитике - приходят по таким запросам...
Gor Reaper Октябрь 2019
Олег, исключительно по конкуренси. У нас есть менеджер который управляет определёнными действиями вроде (register и unregister) где действия - вносим в лист и ремувим. Всё это, менеджер выполняет с живыми людьми, т.е. если человек регистрируется, то - заносим его в лист, если выходит - то убираем из листа. Должны ли быть такие процессы залоченными? Я имею ввиду ReentrantLock, синхронайз блокировки и т.п.? Вообще, работа с многопользовательским менеджером должна лочится при каждом действии этого менеджера если оно включает например пробег по листу (проверки существует ли такой участник уже или нет и т.д.)? Спасибо!
Олег Полторацкий Октябрь 2019
Очень не советую лочить коллекцию во время пробежки по ней. Если используете обычный ArrayList то придется блокировать целый список. Советую использовать CopyOnWriteArrayList - он создает новую копию каждый раз, когда происходят изменения. Плюс к этому гарантируется, что не будет выбрасываться ConcurrentModificationException при использовании итераторов. И самое главное - нет необходимости блокировать список во время перебора. Думаю стоит описать эту коллекцию отдельной статьей.
Gor Reaper Сентябрь 2019
А если конкурент используется вот в такой связке: private static final Map<Object, List<Object>> participians = new ConcurrentHashMap<>();
И в дальнейшем participians.get(object).add(element);
Это считается как частая запись? ;D
Олег Полторацкий Сентябрь 2019
В вашем примере map используется на чтение и будет отдавать очень быстро, поэтому да, ConcurrentHashMap подходит. Но так же нужно думать о конкурентности списка (list).
Нюансы Java разработки
Блог о Java разработке и в деталях описывает наиболее интересные темы