Подтвердить что ты не робот

Является ли ConcurrentHashMap полностью безопасным?

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

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

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

4b9b3361

Ответ 1

Метод 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 является потокобезопасным) значение, вставленное в поток B.

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

Ответ 2

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

Этот класс полностью совместим с 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);
}

Ответ 3

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

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

Ответ 4

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

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

Нити не вызовут друг друга. Код - ThreadSafe.

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

Ответ 5

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

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

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

Ответ 6

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

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