Требуется ли 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
- как следует из названия - позволяет одновременный доступ, с другой стороны. Вы теряете это преимущество, если добавляете синхронизацию.