ConcurrentHashMap возвращает слабо непротиворечивый итератор, зачем нам вообще его использовать?

Я читаю книгу Java Concurrecny на практике. На странице 85 в разделе 5.2.1 говорится о ConcurrentHashMap и его преимуществах. Однако в одной части книги утверждается, что

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

Из того, почему я понимаю, что весь смысл синхронизации в параллельных программах заключается в том, чтобы разрешить потоку доступ к общему ресурсу согласованным образом, тогда как ConcurrentHashMap на самом деле не выполняет этого. Тогда зачем вообще его использовать?


person Hossein    schedule 29.12.2012    source источник


Ответы (3)


Суть в том, чтобы избегать синхронизации, когда она вам не нужна. Если вы не против видеть новые элементы в одних случаях и не видеть их в других, использование итератора ConcurrentHashMap может быть значительно дешевле, чем либо предотвращение добавления элементов другими потоками во время итерации или создание согласованного моментального снимка при создании итератора.

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

person Jon Skeet    schedule 29.12.2012
comment
Есть ли у ConcurrentHashMap синхронизация в своих реализациях? - person Hossein; 29.12.2012
comment
@Hossein: я не знаю навскидку, но вы можете посмотреть на источник так же легко, как и я :) Документы предполагают, что может быть синхронизация при доступе для записи, но не для чтения: однако, хотя все операции потокобезопасны, поиск операции не влекут за собой блокировки, и нет никакой поддержки для блокировки всей таблицы таким образом, чтобы предотвратить любой доступ. - person Jon Skeet; 29.12.2012
comment
@Hossein: IIRC ConcurrentHashMap является реализацией без блокировок, поэтому в нем нет синхронизации в традиционном смысле, но используются атомарные и специальные алгоритмы, чтобы избежать мьютексов. Это делает его быстрее, так как мьютексы могут быть слишком тяжелыми в некоторых приложениях (если не учитывать их стоимость, он позволяет одновременно запускать только 1 поток), но он не полностью синхронизирован. - person Maciej Piechotka; 29.12.2012
comment
@Maciej: ConcurrentHashMap - это реализация чередования блокировок, а уровень параллелизма по умолчанию равен 16. - person Prince John Wesley; 29.12.2012
comment
@PrinceJohnWesley: упс. Извините - мой плохой (увидел ConcurrentHashMap прочитал ConcurrentSkipListMap). - person Maciej Piechotka; 29.12.2012

ConcurrentHashMap по контракту должен быть потокобезопасным. Однако он не должен быть согласованным между потоками.

Когда вы перебираете ConcurrentHashMap, итератор получает копию хеш-карты в тот момент, когда вы ее запросили (и эта копия создается потокобезопасным способом), и вы перебираете эту копию. И да, ничто не гарантирует вам, что пока вы перебираете эту копию, некоторые элементы карты не будут удалены. Но записи карты, которые будут удалены таким образом, все равно будут существовать в вашем итераторе.

person fge    schedule 29.12.2012
comment
Такие записи могут не будут существовать в итераторе. - person Maciej Piechotka; 29.12.2012
comment
Нет, будет. Если бы это была сила, а не сила, вы бы получили ConcurrentModificationException. - person fge; 29.12.2012
comment
Из документации Java SDK они не вызывают ConcurrentModificationException`.. - person Maciej Piechotka; 29.12.2012
comment
Да, потому что во время копирования все записи для данного сегмента хеширования копируются, но они могут быть удалены с карты во время итерации. - person fge; 29.12.2012
comment
Iterator из ConcurrentHashMap не работает против копии. Возможно, он вернет записи, добавленные после создания Iterator, и даже после того, как вы уже получили от него какие-то элементы. Я попробовал это, и иногда это происходит в реальности (Java 8). В противном случае это не было бы слабо непротиворечивым. - person ddekany; 21.09.2017

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

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

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

Правка. Как заметил принц Джон Уэсли, я думал о ConcurrentSkipListMap, когда видел и писал. ConcurrentHashMap. Большинство пунктов остаются в силе, за исключением частей, касающихся отсутствия блокировок (а также всего, что из этого следует, например, гарантированная пропускная способность и т. д.).

person Maciej Piechotka    schedule 29.12.2012