Требуется ли ConcurrentHashMap обернуть в синхронизированный блок?

Нужно ли выполнять все операции без повторения в ConcurrentHashMap (put(), remove() и т.д.) в блок synchronized(this)? Я понимаю, что все эти операции поточно-безопасны, так есть ли реальная польза/необходимость в этом? Используются только операции put() и remove().

protected final Map<String, String> mapDataStore = new ConcurrentHashMap<String, String>();

public void updateDataStore(final String key, final String value) {
    ...
    synchronized (this) {
        mapDataStore.put(key, value);
    }
    ...
}

Ответы

Ответ 1

Нет, вы теряете преимущества ConcurrentHashMap, делая это. Вы можете также использовать HashMap с synchronized или synchronizedMap(), чтобы заблокировать всю таблицу (это то, что вы делаете, когда обертываете операции в synchronized, так как подразумевается монитор - это весь экземпляр объекта.)

Цель ConcurrentHashMap - увеличить пропускную способность вашего параллельного кода, разрешив одновременное чтение/запись в таблице без блокировки всей таблицы. Таблица поддерживает это внутренне, используя блокировку (несколько блокировок вместо одного, с каждой блокировкой, назначенной набору хэш-кодов - см. Java Concurrency на практике от Goetz и другие).

Когда вы используете ConcurrentHashMap, все стандартные методы карты (put(), remove() и т.д.) становятся атомарными в силу блокировки и т.д. в реализации. Единственные компромиссы в том, что такие методы, как size() и isEmpty(), могут не обязательно возвращать точные результаты, поскольку единственный способ, который они могли бы сделать, - это все операции по блокировке всей таблицы.

Интерфейс ConcurrentMap interface > также добавляет новые операции с атомарными соединениями, такие как putIfAbsent() (помещать что-то только в том случае, если ключ еще не на карте), remove() принимает как ключ, так и значение (удаляйте запись только в том случае, если ее значение равно параметру, который вы передаете) и т.д. Эти операции потребовали блокировки всей таблицы, потому что им нужны два вызова метода для выполнения (например, putIfAbsent() нужны вызовы для оба containsKey() и put(), завернутые внутри одного блока synchronized, если вы использовали стандартную реализацию Map.) Еще раз, вы получаете большую пропускную способность с использованием этих методов, избегая блокировки всей таблицы.

Ответ 2

Синхронизация этих операций здесь не принесет пользы - это фактически ухудшает производительность, если вам не нужна синхронизация.

Причина ConcurrentHashMap была создана, так это то, что синхронизированные карты (реализованные вручную как в вопросе или созданные обычным способом с помощью Collections.synchronizedMap(map)) показывают плохую производительность при доступе к множеству потоков. Блокировать операции "Поместить и получить", чтобы все остальные потоки должны были ждать и не могли одновременно получать доступ к карте. ConcurrentHashMap - как следует из названия - позволяет одновременный доступ, с другой стороны. Вы теряете это преимущество, если добавляете синхронизацию.