ConcurrentHashMap и составные операции

Hashtable и Collections.synchronizedMap являются потокобезопасными, но все же составными операциями типа

if (!map_obj.containsKey(key)) {
   map_obj.put(key, value);
}

требуется внешняя синхронизация:

synchronized(map_obj) {
    if (!map_obj.containsKey(key)) {
       map_obj.put(key, value);
    }
}

Предположим, что вместо Hashtable или HashMap мы имеем ConcurrentHashMap (CHM). CHM предоставляет альтернативный метод putIfAbsent() для вышеуказанной операции соединения, тем самым устраняя необходимость внешней синхронизации.

Но предположим, что не существует putIfAbsent(), предоставленного CHM. Затем мы можем написать следующий код:

synchronized(concurrenthashmap_obj) {
    if (!concurrenthashmap_obj.containsKey(key)) {
       concurrenthashmap_obj.put(key, value);
    }
}

Я имею в виду, можно ли использовать внешнюю синхронизацию на объекте CHM? Будет ли это работать?

Для операции над сложным соединением существует метод putIfAbsent() в CHM, но как мы можем добиться безопасности потоков для других сложных операций, если мы используем CHM. Я имею в виду, можно ли использовать внешнюю синхронизацию на объекте CHM?

Ответ 1

Нет, вы не можете использовать внешнюю синхронизацию, чтобы обеспечить атомарность составных операций над ConcurrentHashMap.

Чтобы быть точным, вы можете использовать внешнюю синхронизацию, чтобы обеспечить атомарность составных операций, но только если все операции с ConcurrentHashMap синхронизированы по одной и той же блокировке (хотя использование ConcurrentHashMap не имеет смысла в этом case - вы можете заменить его на обычный HashMap).

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

Однако ConcurrentHashMap предоставляет количество методов, которые могут быть использованы для реализации сложных операций оптимистично:

  • putIfAbsent(key, value)
  • remove(key, value)
  • replace(key, value)
  • replace(key, oldValue, newValue)

Вы можете использовать эту операцию для реализации определенных составных операций без эксплицитной синхронизации, так же, как и для AtomicReference и т.д.

Ответ 2

Нет причин, по которым вы не можете. Традиционная синхронизация работает со всем, для них нет особых исключений. ConcurrentHashMaps просто используют более оптимизированные механизмы защиты потоков, если вы хотите сделать что-то более сложное, возврат к традиционной синхронизации может быть вашим единственным вариантом (с использованием и блокировкой).

Ответ 3

Вы всегда можете использовать блок synchronized. Причудливые коллекции в java.util.concurrent не запрещают это, они просто делают его излишним для большинства распространенных случаев использования. Если вы выполняете сложную операцию (например, вы хотите вставить два ключа, которые всегда должны иметь одинаковое значение), вы не только можете использовать внешнюю синхронизацию - вы должны.

например:.

String key1 = getKeyFromSomewhere();
String key2 = getKeyFromSomewhereElse();
String value = getValue();

// We want to put two pairs in the map - [key1, value] and [key2, value]
// and be sure that in any point in time both key1 and key2 have the same 
// value
synchronized(concurrenthashmap_obj) {
    concurrenthashmap_obj.put(key1, value);

    // without external syncronoziation, key1 value may have already been
    // overwritten from a different thread!
    concurrenthashmap_obj.put(key2, value);
}

Ответ 4

Поскольку ConcurrentHashMap реализует интерфейс карты, он поддерживает все функции, которые выполняет каждая базовая карта. Итак, да: вы можете использовать его, как и любую другую карту, и игнорировать все дополнительные функции. Но тогда у вас будет по существу медленный HashMap.

Основное различие между синхронизированной картой и параллельной картой - как указано в названии - concurrency. Представьте, что у вас есть 100 потоков, которые нужно читать с карты, если вы synchronize заблокируете 99 потоков и 1 можете выполнить эту работу. Если вы используете concurrency, 100 потоков могут работать одновременно.

Теперь, если вы думаете о фактической причине, по которой вы используете потоки, вы вскоре приходите к выводу, что вы должны избавиться от всех возможных блоков synchronized, которые вы можете.

Ответ 5

Все зависит от того, что вы подразумеваете под "другой составной операцией" и "работой". Синхронизация работает с ConcurrentHashMap точно так же, как и с любым другим объектом.

Итак, если вы хотите, чтобы какое-то сложное изменение общего состояния рассматривалось как атомное изменение, все обращения к этому общему состоянию должны быть синхронизированы при одной и той же блокировке. Эта блокировка может быть самой Картой, или это может быть другой объект.

Ответ 6

О java.util.concurrent.ConcurrentHashMap

  • "полностью совместим с Hashtable в программах, которые полагаются на безопасность потока, но не на его детали синхронизации: они не бросают ConcurrentModificationException."

  • "позволяет concurrency выполнять операции update

О java-памяти

Вообще говоря, считывания безопасны с точки зрения синхронизации, но не с точки зрения памяти.

См. также " http://www.ibm.com/developerworks/java/library/j-jtp03304/".

Значения synchronizaton и volatile должны использоваться для управления параллельным чтением (против записи).

О putIfAbsent

putIfAbsent является вашим другом:

Если указанный ключ еще не связан со значением, соедините он с данным

value. This is equivalent to
   if (!map.containsKey(key))
       return map.put(key, value);
   else
       return map.get(key);

за исключением того, что действие выполняется!!! атомарно!!!.