ConcurrentHashMap полностью безопасен?

это отрывок из JavaDoc относительноConcurrentHashMap, В нем говорится, что операции поиска обычно не блокируются, поэтому могут перекрываться с операциями обновления. Означает ли этоget() метод не является потокобезопасным? "

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

Операции извлечения (включая get) обычно не блокируются, поэтому могут перекрываться с операциями обновления (включая put и remove). Извлечения отражают результаты самых последних завершенных операций обновления, проводимых с момента их появления ».

 Aurand19 февр. 2013 г., 01:29
Если я'Если вы правильно понимаете, это означает, что поиск всегда будет возвращать результаты последнего обновления, которое должно быть завершено - во время начала поиска -. Это означает, что полное обновление может произойти между моментом, когда поиск начинается и завершается, и это не изменит результат указанного поиска.

Ответы на вопрос(6)

как он защищен от потоков, может оказаться не таким, как вы ожидаете. Есть некоторыенамеки» вы можете увидеть из:

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

Чтобы узнать всю историю в более полной картине, вам нужно знать оConcurrentMap интерфейс.

ОригиналMap предоставляет некоторые очень простые методы чтения / обновления. Даже я смог сделать потокобезопасную реализациюMap; Есть много случаев, когда люди не могут использовать мою Карту без учета моего механизма синхронизации. Это типичный пример:

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

Этот кусок кода не является потокобезопасным, даже если сама карта. Два потока вызоваcontainsKey() в то же время мог подумать, что нет такого ключа, который они оба вставляют в.Map

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

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

Такой дополнительный код требует, чтобы вы знали одетали синхронизации " карты. В приведенном выше примере нам нужно знать, что синхронизация достигается с помощьюсинхронизирован».

ConcurrentMap Интерфейс сделать этот шаг вперед. Это определяет некоторые общие "сложный" действия, которые включают в себя множественный доступ к карте. Например, приведенный выше пример представлен какputIfAbsent(), С этими "сложный" действия, пользователиConcurrentMap (в большинстве случаев) неНеобходимо синхронизировать действия с множественным доступом к карте. Следовательно, реализация Map может выполнять более сложный механизм синхронизации для повышения производительности.ConcurrentHashhMap хороший пример Потокобезопасность фактически поддерживается сохранением отдельных блокировок для разных разделов карты. Он поточно-ориентирован, поскольку одновременный доступ к карте не повредит внутреннюю структуру данных и не приведет к неожиданному потере любого обновления и т. Д.

Учитывая все вышесказанное, значение Javadoc будет более понятным:

Операции поиска (в том числе get) обычно не блокируют » так какConcurrentHashMap не используетсинхронизируется» для его безопасности потока. Логикаget сама заботится о безопасности потоков; и если вы посмотрите дальше в Javadoc:

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

Мало того, что поиск неблокируется, даже обновления могут происходить одновременно. Однако неблокирующее / одновременное обновление не означает, что оно поточно-небезопасно. Это просто означает, что он использует не простосинхронизируется» для безопасности потока.

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

// only remove if both key1 and key2 exists
if (map.containsKey(key1) && map.containsKey(key2)) {
    map.remove(key1);
    map.remove(key2);
}

ConcurrentHashmap.get() является потокобезопасным, в том смысле, что

Не будет выбрасывать никаких исключений, в том числеConcurrentModificationExceptionОн вернет результат, который был верным в некоторое (недавнее) время в прошлом. Это означает, что два параллельных вызова для получения могут возвращать разные результаты. Конечно, это верно для любого другогоMap также.
 Gray19 февр. 2013 г., 01:44
Обратные вызовы, которые можно получить, могут возвращать разные результаты, если было вмешательствоput() или другая операция, которая изменяет карту.
 Miserable Variable20 февр. 2013 г., 01:35
@AdrianShum Я слышу тебя. Я пытался упростить для кого-то, кто показался мне относительно начинающим разработчиком.
 AKS20 авг. 2013 г., 07:17
Я думаю, что это связано с тем, что значение объекта Volatile в HashEntry.
 user69791119 февр. 2013 г., 01:28
Что вы подразумеваете под "два последовательных вызова, чтобы получить "?
 Lee Meador19 февр. 2013 г., 01:31
Вызов 'получить()' и сохранить результат. Вызов 'получить()' второй раз и сравните результаты. Тот'с чемспина к спине' средства.
 Adrian Shum20 февр. 2013 г., 02:10
@MiserableVariable Однако даже он новичок, это не такЯ имею в виду, что это нормально дать ему неправильный ответ (Извините, я не вижу ответа "упрощение», но просто неактуально и некорректно)
 Adrian Shum19 февр. 2013 г., 03:44
Я неЯ не совсем согласен с вашим ответом, потому что: 1. Не выбрасывать исключение не означает, что оно поточно-ориентировано, а исключение ConcurrentModificationException не относится к поточно-ориентированному. 2. "тот же результат для двухкратного вызова " никогда не является контрактом для Map, и это также не имеет ничего общего с безопасностью потоков.

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

Подумайте об обновлении статьи, в которой говорится, где Боб. Если один поток спрашивает, где находится Боб, примерно в то же время, когда другой поток обновляется, чтобы сообщить, что он пришелвнутри», вы можете'не могу предсказать, получит ли читательский поток Бобастатус каквнутри» или же 'вне', Даже если поток обновления сначала вызывает метод, поток чтения может получитьвне' статус.

Потоки не будут вызывать проблем друг у друга. Код является ThreadSafe.

Одна нить выигралазайти в бесконечный цикл или начать генерировать странные исключения NullPointerExceptions или получить "itside» с половиной старого статуса и половиной нового.

 viki.omega906 мар. 2014 г., 11:21
Был быдвойная проверка блокировки шаблон решить проблему? Вынуждая нить, которая ищетвнутри» Значение, чтобы проверить дважды, будет ли поток записи иметь возможность обновить значение? Таким образом, вы блокируете записи, но не читаете?

Функция get () в ConcurrentHashMap является поточно-ориентированной, поскольку она считывает значение, которое является Volatile. А в случаях, когда значение равно нулю для любого ключа, метод get () ждет, пока не получит блокировку, а затем считывает обновленное значение.

когдаput() Метод обновляет CHM, затем устанавливает значение этого ключа в null, а затем создает новую запись и обновляет CHM. Это нулевое значение используетсяget() метод как сигнал, что другой поток обновляет CHM с тем же ключом.

 Abdullah Khan18 сент. 2018 г., 09:07
When put() method is updating CHM, then it sets the value of that key to null, Пояснения к тому же, пожалуйста! Есть ссылки? Код?

get() Этот метод является потокобезопасным, и другие пользователи дали вам полезные ответы по этой конкретной проблеме.

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

if (!map.containsKey(key)) 
   return map.put(key, value);
else
   return map.get(key);

В многопоточной среде это условие гонки. Вы должны использоватьConcurrentHashMap.putIfAbsent(K key, V value) и обратите внимание на возвращаемое значение, которое сообщает вам, была ли успешной операция put. Прочитайте документы для более подробной информации.

Отвечая на комментарий, который просит разъяснить, почему это условие гонки.

Представьте, что есть две темы,AB которые собираются поместить два разных значения в карту,v1 а такжеv2 соответственно, имея одинаковый ключ. Ключ изначально отсутствует на карте. Они чередуются таким образом:

НитьA звонкиcontainsKey и обнаруживает, что ключа нет, но немедленно приостанавливается.НитьB звонкиcontainsKey и обнаруживает, что ключ отсутствует, и имеет время, чтобы вставить его значение.v2НитьA резюме и вставки "v1мирно» перезаписывать (так какput является потокобезопасным) значение, вставленное потоком.B

Сейчас нитьB "считает, что» он успешно вставил свою собственную ценностьv2, но карта содержитv1, Это действительно катастрофа, потому что потокB может позвонитьv2.updateSomething() и воля "считать" что потребители карты (например, другие потоки) имеют доступ к этому объекту и увидят, что это может быть важным обновлением ("как: этот IP-адрес посетителя пытается выполнить DOS, отклонить все запросы с этого момента "). Вместо этого объект скоро будет собран и утерян мусором ».

 gd109 мар. 2014 г., 10:49
@javaseeker: я обновил свой ответ
 gd119 февр. 2013 г., 01:43
Для замены вставки я имею в виду: вы меняете HashMap на ConcurrentHashMap и все обычные операции на карте (например, ручной работы)поставить если отсутствует - ямы видели это миллионы раз) автоматически ориентированы на многопоточность.
 user187040027 мая 2016 г., 05:25
с тем же чередованием, что произойдет, если мы используем putIfAbsent? v1 будет отброшен? Что если мы хотим разрешить перезаписывать значение одним и тем же ключом? Нужно ли проверять цикл, чтобы убедиться, что запись прошла успешно? Если так, то как это может быть эффективно, чем "синхронизированный блок "?
 Gray19 февр. 2013 г., 01:46
буду переделывать так сказатьхотяConcurrentHashMap капля в поточно-ориентированной замене HashMap, важно понимать, что если вы выполняете несколько операций ... »
 gd119 февр. 2013 г., 01:47
Хорошая точка зрения. Изменено по вашему предложению!
 java seeker09 мар. 2014 г., 05:56
почему приведенный фрагмент кода имеет состояние гонки? такое ConcurrentHashmap.put () не является потокобезопасным? Я запутался
 Novice User28 окт. 2018 г., 18:52
@ user1870400:synchronized Блок заблокирует все ключи. Мы можем получить лучшую производительность, если сделаем ее гранулярной, основанной на разных ключах. Вот гдеConcurrentHashMap может быть полезным метод putIfAbsent вConcurrentHashMap это метод проверки-если-отсутствует-затем-установить. Это'Это атомарная операция. Так что это предотвратит переопределение значения другим потоком, если предыдущий уже записал его.
 Gray19 февр. 2013 г., 01:45
Согласовано. Но твое утверждениеConcurrentHashMap не является поточно-безопасной заменой HashMap " чрезвычайно вводит в заблуждение по крайней мере. Я'сказал бы этонеправильно.я
 Gray19 февр. 2013 г., 01:42
не поточная безопасная замена HashMap ", Ну, конечно. Вы правы, что есть условия гонки с несколькими операциями, но это нене останавливайсяConcurrentHashMap от того, чтобы быть потокобезопасным.

HashMap поделен на "ковши» на основании.hashCodeConcurrentHashMap использует этот факт. Его механизм синхронизации основан на блокировании сегментов, а не на всейMap, Таким образом, несколько потоков могут одновременно записывать в несколько разных сегментов (один поток может записывать в один интервал одновременно).

Чтение изConcurrentHashMap почти Безразлично»использовать синхронизацию Синхронизация используется, когда при получении значения для ключа он видитnull значение, посколькуConcurrentHashMap Можно'т магазинnull в качестве значений (да, кроме ключей, значения также могутт бытьnulls) это предполагает, что выборкаnull во время чтения произошло в середине инициализации картызапись (пара ключ-значение) другим потоком: когда ключ был назначен, но значения еще нет, и он все еще остается в силедефолт ноль.

В таком случае читающему потоку нужно будет подождать, пока запись будет записана полностью.

Итак, результаты изread() будет основываться на текущем состоянии карты. Если вы прочитаете значение ключа, который находился в процессе обновления, вы, вероятно, получите старое значение, так как процесс записи не завершился.Я еще не закончил.

Ваш ответ на вопрос