1. Обзор
В этом уроке мы обсудим различия между коллекциями .synchronizedMap() и ConcurrentHashMap .
Кроме того, мы рассмотрим результаты производительности операций чтения и записи для каждого из них.
2. Различия
Коллекции.synchronizedMap() и ConcurrentHashMap обеспечивают потокобезопасные операции с коллекциями данных.
Класс утилиты Collections предоставляет полиморфные алгоритмы, которые работают с коллекциями и возвращают завернутые коллекции . Его метод synchronizedMap() обеспечивает потокобезопасную функциональность.
Как следует из названия, synchronizedMap() возвращает синхронизированную карту , подкрепленную Картой , которую мы предоставляем в параметре. Чтобы обеспечить потокобезопасность, synchronizedMap() разрешает всем доступам к резервной копии Map через возвращенную Map .
ConcurrentHashMap был представлен в JDK 1.5 как усовершенствование HashMap , которое поддерживает высокий параллелизм для извлечения, а также обновления . HashMap не является потокобезопасным, поэтому это может привести к неправильным результатам во время конкуренции потоков.
Класс ConcurrentHashMap потокобезопасен. Таким образом, несколько потоков могут работать с одним объектом без каких-либо осложнений.
В ConcurrentHashMap операции чтения не блокируются, в то время как операции записи блокируют определенный сегмент или сегмент. Уровень ведра или параллелизма по умолчанию равен 16, что означает, что 16 потоков могут писать в любой момент после блокировки сегмента или ведра.
2.1. Исключение ConcurrentModificationException
Для таких объектов , как HashMap , выполнение параллельных операций не допускается. Поэтому, если мы попытаемся обновить HashMap во время итерации по нему, мы получим ConcurrentModificationException . Это также произойдет при использовании synchronizedMap() :
@Test(expected = ConcurrentModificationException.class) public void whenRemoveAndAddOnHashMap_thenConcurrentModificationError() { Mapmap = new HashMap<>(); map.put(1, "baeldung"); map.put(2, "HashMap"); Map synchronizedMap = Collections.synchronizedMap(map); Iterator > iterator = synchronizedMap.entrySet().iterator(); while (iterator.hasNext()) { synchronizedMap.put(3, "Modification"); iterator.next(); } }
Однако это не относится к ConcurrentHashMap :
Mapmap = new ConcurrentHashMap<>(); map.put(1, "baeldung"); map.put(2, "HashMap"); Iterator > iterator = map.entrySet().iterator(); while (iterator.hasNext()) { synchronizedMap.put(3, "Modification"); iterator.next() } Assert.assertEquals(3, map.size());
2.2. поддержка null
Коллекции.synchronizedMap() и ConcurrentHashMap |/обрабатывают null ключи и значения по-разному .
ConcurrentHashMap не допускает null в ключах или значениях:
@Test(expected = NullPointerException.class) public void allowNullKey_In_ConcurrentHasMap() { Mapmap = new ConcurrentHashMap<>(); map.put(null, 1); }
Однако при использовании коллекций.synchronizedMap() , null поддержка зависит от ввода Map . Мы можем иметь один null в качестве ключа и любое количество null значений при коллекциях.synchronizedMap() поддерживается HashMap или LinkedHashMap, в то время как если мы используем TreeMap , у нас могут быть null значения, но не null ключи.
Давайте предположим, что мы можем использовать ключ null для коллекций .synchronizedMap() при поддержке HashMap :
Mapmap = Collections .synchronizedMap(new HashMap ()); map.put(null, 1); Assert.assertTrue(map.get(null).equals(1));
Аналогично, мы можем проверить поддержку null в значениях для обеих коллекций .synchronizedMap() и ConcurrentHashMap .
3. Сравнение производительности
Давайте сравним производительность ConcurrentHashMap и Коллекций.synchronizedMap(). В этом случае мы используем фреймворк с открытым исходным кодом Java Microbenchmark (JMH) для сравнения производительности методов в наносекундах .
Мы провели сравнение для случайных операций чтения и записи на этих картах. Давайте быстро взглянем на наш тестовый код JMH:
@Benchmark public void randomReadAndWriteSynchronizedMap() { Mapmap = Collections.synchronizedMap(new HashMap ()); performReadAndWriteTest(map); } @Benchmark public void randomReadAndWriteConcurrentHashMap() { Map map = new ConcurrentHashMap<>(); performReadAndWriteTest(map); } private void performReadAndWriteTest(final Map map) { for (int i = 0; i < TEST_NO_ITEMS; i++) { Integer randNumber = (int) Math.ceil(Math.random() * TEST_NO_ITEMS); map.get(String.valueOf(randNumber)); map.put(String.valueOf(randNumber), randNumber); } }
Мы провели наши тесты производительности, используя 5 итераций с 10 потоками для 1000 элементов. Давайте посмотрим на результаты тестирования:
Benchmark Mode Cnt Score Error Units MapPerformanceComparison.randomReadAndWriteConcurrentHashMap avgt 100 3061555.822 ± 84058.268 ns/op MapPerformanceComparison.randomReadAndWriteSynchronizedMap avgt 100 3234465.857 ± 60884.889 ns/op MapPerformanceComparison.randomReadConcurrentHashMap avgt 100 2728614.243 ± 148477.676 ns/op MapPerformanceComparison.randomReadSynchronizedMap avgt 100 3471147.160 ± 174361.431 ns/op MapPerformanceComparison.randomWriteConcurrentHashMap avgt 100 3081447.009 ± 69533.465 ns/op MapPerformanceComparison.randomWriteSynchronizedMap avgt 100 3385768.422 ± 141412.744 ns/op
Приведенные выше результаты показывают, что ConcurrentHashMap работает лучше, чем Коллекции.synchronizedMap() .
4. Когда использовать
Мы должны отдать предпочтение Коллекциям.synchronizedMap() когда согласованность данных имеет первостепенное значение, и мы должны выбрать ConcurrentHashMap для критически важных для производительности приложений, где операций записи гораздо больше, чем операций чтения.
5. Заключение
В этой статье мы продемонстрировали различия между ConcurrentHashMap и Коллекциями.synchronizedMap() . Мы также показали производительность обоих из них, используя простой тест JMH.
Как всегда, примеры кода доступны на GitHub .