2013-02-19 8 views
31

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

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

операция Retrieval (включая ГЮТ), как правило, не блокируют, поэтому может перекрытия с операциями обновления (в том числе поставить и удалить). извлечения отражают результаты последнего опроса ete update , держась за свое начало. "

+0

Если я читаю это право, это означает, что поиск всегда будет возвращать результаты последнего обновления для завершения - в момент начала поиска. Это означает, что полное обновление может происходить между временем, когда поиск начинается и завершается, и он не изменит результат упомянутого поиска. – Aurand

ответ

8

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

  • Он не будет бросать никаких исключений, в том числе ConcurrentModificationException
  • Это будет возвращать результат, который был истинным в какой-то (недавно) время в прошлом , Это означает, что два обратных обращения к получателю могут возвращать разные результаты. Конечно, это относится и к любому другому Map.
+0

Что вы подразумеваете под «двумя обратными обратными вызовами»? – user697911

+0

Вызовите 'get()' и сохраните результат. Вызовите 'get()' второй раз и сравните результаты. Вот что означает «спина к спине». –

+2

Обратные вызовы get могут возвращать разные результаты, если была промежуточная 'put()' или другая операция, которая модифицирует карту. – Gray

4

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

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

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

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

+0

Удалил бы проблему с шаблоном [double check checked] (https://en.wikipedia.org/wiki/Double-checked_locking)? Заставляя поток, который ищет значение «внутри» для проверки дважды, может ли поток писателя обновить значение? Таким образом, вы блокируете запись, но не читаете? –

33

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

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

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

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


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

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

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

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

+0

«не для потоковой замены для HashMap». Конечно, это так. Вы правы, что существуют условия гонки с несколькими операциями, но это не мешает «ConcurrentHashMap» быть потокобезопасным. – Gray

+0

Для замены взамен я имею в виду: вы меняете свой HashMap на ConcurrentHashMap, и все обычные операции на карте (например, ручной, если он отсутствует, - я видел его в два раза) - это автоматическая потокобезопасность. – gd1

+0

Согласовано. Но ваше утверждение «ConcurrentHashMap не является потокобезопасной заменой для HashMap» крайне мало вводит в заблуждение. Я бы сказал, что это неправильно. – Gray

1

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

Чтение от ConcurrentHashMapпочти не использует механизмы синхронизации. Я сказал почти потому, что использует синхронизацию, когда он получит nullзначение. Исходя из факта, что ConcurrentHashMap не может хранить значения null (да, значения также не могут быть значениями), если он получит это значение, это означает, что чтение было вызвано в середине написания пары ключ-значение (после того, как запись была создана для но его значение еще не было установлено - это было null). В этом случае поток чтения должен будет дождаться окончания записи.

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

9

Это поточно-безопасный. Тем не менее, способ быть потокобезопасным может быть не тем, что вы ожидаете.Есть некоторые «подсказки» вы можете видеть:

Этот класс полностью совместим с 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 (в большинстве случаев) не требуется синхронизировать действия с множественным доступом к карте. Следовательно, реализация карты может выполнять более сложный механизм синхронизации для повышения производительности. 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); 
} 
2

ПОЛУЧИТЬ() в ConcurrentHashMap потокобезопасно, потому что он считывает значение которое Летучий. И в случаях, когда значение равно нулю любого ключа, тогда метод get() ждет, пока он не получит блокировку, а затем прочитает обновленное значение .

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

Смежные вопросы