Java ConcurrentHashMap.computeIfPresent видимость изменения значения

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

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) после операции обновления.

Я не уверен, как интерпретировать документы, но мне кажется, что отношения "до того" гарантируют видимость изменений в основной коллекции в этом конкретном случае.

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

3 ответа

Решение

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

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

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

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

Соедините, ответ - да.

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

Тот факт, что этот метод задокументирован как 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) обычно не блокируются, поэтому могут перекрываться с операциями обновления (включая put и remove).

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

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

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

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

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

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

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

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

Другие вопросы по тегам