Нужно ли заворачивать 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);
    }
    ...
}

person MeanwhileInHell    schedule 23.09.2014    source источник


Ответы (2)


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

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

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

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

person sparc_spread    schedule 23.09.2014

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

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

person kapex    schedule 23.09.2014
comment
Что, если кодовый блок содержит оба метода containsKey() и put()? Это должно быть синхронизировано или нет? - person Gaurav; 11.04.2018
comment
@gaurav Вероятно, это следует синхронизировать. Но вместо синхронизации я бы предпочел использовать методы putIfAbsent или computeIfAbsent (оба являются атомарными операциями для ConcurrentMap). - person kapex; 11.04.2018
comment
2: А что может быть, если используется Java 1.7? - person Gaurav; 13.04.2018