Java ConcurrentHashMap.computeIfPresent видимость изменения значения - PullRequest
0 голосов
/ 30 сентября 2018

Допустим, у меня есть параллельная карта с коллекциями в качестве значения:

Map<Integer, List<Integer> map = new ConcurrentHashMap<>();
map.putIfAbsent(8, new ArrayList<>());

, и я обновляю значение следующим образом:

map.computeIfPresent(8, (i, c) -> {
    c.add(5);
    return c;
});

Я знаю, что computeIfPresent весь вызов методавыполняется атомарно.Однако, учитывая, что к этой карте одновременно обращаются несколько потоков, меня немного беспокоит видимость данных изменений, внесенных в базовую коллекцию.В этом случае значение 5 будет отображаться в списке после вызова map.get

Мой вопрос будет изменен на список, который будет виден в других потоках после вызова map.get, если изменения будут выполнены в методе computeIfPresentзвоните.

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

Я не уверен, как интерпретировать документы, но, похоже,В этом конкретном случае отношение «случается до» гарантирует видимость изменений в базовой коллекции

Более формально, операция обновления для данного ключа имеет отношение «происходит до» с любым (неnull) поиск для этого ключа, сообщающий об обновленном значении

Ответы [ 3 ]

0 голосов
/ 30 сентября 2018

Тот факт, что этот метод задокументирован как atomic, не имеет большого значения для visibility (если это не является частью документации).Например, чтобы сделать это проще:

// some shared data
private List<Integer> list = new ArrayList<>();

public synchronized void addToList(List<Integer> x){
     list.addAll(x);
}

public /* no synchronized */ List<Integer> getList(){
     return list;
}

Мы можем сказать, что addToList действительно атомарный, только один поток может вызвать его за один раз.Но как только определенный поток вызывает getList - просто нет гарантии относительно visibility (потому что для того, чтобы это установить, это должно произойти с той же блокировкой ).Таким образом, видимость - это то, что происходит до того, как дело касается, и документация computeIfPresent ничего об этом не говорит.

Вместо этого документация класса гласит:

Операции получения (включая получение) обычно не блокируются, поэтому может перекрываться с операциями обновления (включая операции удаления и удаления),

Ключевым моментом здесь, очевидно, является перекрытие , поэтому некоторые другие потоки, вызывающие get (получая, таким образом, List), могут видеть, что List вкакое-то государство;не обязательно состояние, в котором computeIfPresent начался (до того, как вы на самом деле позвонили get).Обязательно прочитайте дальше, чтобы понять, что на самом деле может означать some .

А теперь самое сложное в этой документации:

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

Прочитайте это предложениео завершено снова, то, что он говорит, это то, что единственное, что вы можете прочитать, когда поток get это последний завершенный состояние, в котором был List. И теперь следующее предложение говоритчто происходит до , установленного между двумя действиями.

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

Итак, чтобы ответить на ваш вопрос, все потоки, вызывающие get, увидят last completed action, произошедшую с этим ключом;в вашем случае, если вы можете гарантировать этот порядок, я бы сказал, да, они будут видны.

0 голосов
/ 01 октября 2018

Чтобы уточнить ваш вопрос:

Вы предоставляете некоторую внешнюю гарантию, такую, что Map.computeIfPresent() - это , вызываемый ранее Map.get().

Вы не указали, как вы это делаете, но, допустим, вы делаете это, используя что-то с семантикой «происходит до» , предоставляемой JVM.Если это так, то одна только гарантия List.add() видна потоку, вызывающему Map.get() просто ассоциацией отношения произойдет до .

Теперь, чтобы ответить на вопрос, который вы на самом деле задаете: как вы заметили, существует случайное до отношение между операцией обновления ConcurrentHashMap.computeIfPresent() и последующим вызовом метода доступа ConcurrentMap.get().И, естественно, между List.add() и концом ConcurrentHashMap.computeIfPresent().

есть отношение до-* * * * * * * * * * * * * * * * * * * * * * * * * * *1032* Если сложить вместе, ответ будет Да .

Есть гарантия, что другой поток увидит 5 в List, полученном через Map.get(), при условии вы гарантируете Map.get() на самом деле вызывается после computeIfPresent() заканчивается (как указано в вопросе).Если последняя гарантия нарушается, и Map.get() как-то вызывается до окончания computeIfPresent(), нет никаких гарантий того, что увидит другой поток, поскольку ArrayList не является потокобезопасным.

0 голосов
/ 30 сентября 2018

c.add(5) не является поточно-ориентированным, внутреннее состояние c не защищено картой.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...