Рубрики
Без рубрики

Коллекции.Синхронизированная карта против ConcurrentHashMap

Изучите различия между коллекциями.synchronizedMap и ConcurrentHashMap.

Автор оригинала: baeldung.

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() {
    Map map = 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 :

Map map = 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() {
    Map map = new ConcurrentHashMap<>();
    map.put(null, 1);
}

Однако при использовании коллекций.synchronizedMap() , null поддержка зависит от ввода Map . Мы можем иметь один null в качестве ключа и любое количество null значений при коллекциях.synchronizedMap() поддерживается HashMap или LinkedHashMap, в то время как если мы используем TreeMap , у нас могут быть null значения, но не null ключи.

Давайте предположим, что мы можем использовать ключ null для коллекций .synchronizedMap() при поддержке HashMap :

Map map = 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() {
    Map map = 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 .