1. Обзор
В этом руководстве мы обсудим различия между Collections.synchronizedMap()
и ConcurrentHashMap
.
Кроме того, мы рассмотрим результаты производительности операций чтения и записи для каждого из них.
2. Различия
Collections.synchronizedMap()
и ConcurrentHashMap
обеспечивают потокобезопасные операции с коллекциями данных.
Вспомогательный класс Collections
предоставляет полиморфные алгоритмы, которые работают с коллекциями и возвращают упакованные коллекции . Его метод synchronizedMap()
обеспечивает потокобезопасную функциональность.
Как следует из названия, synchronizedMap()
возвращает синхронизированную карту
, поддерживаемую картой
, которую мы предоставляем в параметре. Чтобы обеспечить потокобезопасность, synchronizedMap()
разрешает все обращения к резервной карте
через возвращаемую карту
.
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() {
Map<Integer, String> map = new HashMap<>();
map.put(1, "foreach");
map.put(2, "HashMap");
Map<Integer, String> synchronizedMap = Collections.synchronizedMap(map);
Iterator<Entry<Integer, String>> iterator = synchronizedMap.entrySet().iterator();
while (iterator.hasNext()) {
synchronizedMap.put(3, "Modification");
iterator.next();
}
}
Однако это не относится к ConcurrentHashMap
:
Map<Integer, String> map = new ConcurrentHashMap<>();
map.put(1, "foreach");
map.put(2, "HashMap");
Iterator<Entry<Integer, String>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
map.put(3, "Modification");
iterator.next()
}
Assert.assertEquals(3, map.size());
2.2. нулевая
поддержка
Collections.synchronizedMap()
и ConcurrentHashMap по-разному
обрабатывают нулевые
ключи и значения .
ConcurrentHashMap
не допускает null
в ключах или значениях:
@Test(expected = NullPointerException.class)
public void allowNullKey_In_ConcurrentHasMap() {
Map<String, Integer> map = new ConcurrentHashMap<>();
map.put(null, 1);
}
Однако при использовании Collections.synchronizedMap()
поддержка null
зависит от входной Map
.
У нас может быть один нуль
в качестве ключа и любое количество нулевых
значений, когда Collections.synchronizedMap()
поддерживается HashMap
или LinkedHashMap,
тогда как, если мы используем TreeMap
, у нас могут быть нулевые
значения, но не нулевые
ключи.
Давайте утверждаем, что мы можем использовать нулевой
ключ для Collections.synchronizedMap()
, поддерживаемый HashMap
:
Map<String, Integer> map = Collections
.synchronizedMap(new HashMap<String, Integer>());
map.put(null, 1);
Assert.assertTrue(map.get(null).equals(1));
Точно так же мы можем проверить поддержку null
в значениях для Collections.synchronizedMap()
и ConcurrentHashMap
.
3. Сравнение производительности
Давайте сравним производительность ConcurrentHashMap
и Collections.synchronizedMap().
В этом случае мы используем платформу с открытым исходным кодом Java Microbenchmark Harness (JMH) для сравнения производительности методов в наносекундах .
Мы провели сравнение случайных операций чтения и записи на этих картах. Давайте быстро взглянем на наш тестовый код JMH:
@Benchmark
public void randomReadAndWriteSynchronizedMap() {
Map<String, Integer> map = Collections.synchronizedMap(new HashMap<String, Integer>());
performReadAndWriteTest(map);
}
@Benchmark
public void randomReadAndWriteConcurrentHashMap() {
Map<String, Integer> map = new ConcurrentHashMap<>();
performReadAndWriteTest(map);
}
private void performReadAndWriteTest(final Map<String, Integer> 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
работает лучше, чем Collections.synchronizedMap()
.
4. Когда использовать
Мы должны отдавать предпочтение Collections.synchronizedMap()
, когда согласованность данных имеет первостепенное значение, и мы должны выбирать ConcurrentHashMap
для критически важных приложений, где операций записи гораздо больше, чем операций чтения.
Это связано с тем, что Collections.synchronizedMap()
требует, чтобы каждый поток блокировал весь объект как для операций чтения, так и для записи. Для сравнения, ConcurrentHashMap
позволяет потокам блокировать отдельные сегменты коллекции и одновременно вносить изменения.
5. Вывод
В этой статье мы продемонстрировали различия между ConcurrentHashMap
и Collections.synchronizedMap()
. Мы также продемонстрировали производительность обоих из них, используя простой тест JMH.
Как всегда, образцы кода доступны на GitHub .