Предположим, что потоки A
и B
имеют ссылку на экземпляр Container
.
(Поскольку Container.map
объявлен как final
, его значение будет безопасно опубликовано. Кроме того, , поскольку он ссылается на ConcurrentHashMap
, синхронизация не требуется для обеспечения безопасности потока.)
Теперь предположим, что поток A
вызывает Longholder holder = container.createValue(x)
и каким-то образом передает holder
потоку B
. Ваш вопрос: если B
звонит doSmth
, проходя мимо этого держателя, увидит ли map.get(holder)
звонок его на карте?
Ответ - Да, будет.
Спецификация для ConcurrentHashMap
это говорит:
"Извлечения отражают результаты самых последних завершенных операций обновления, выполненных с момента их появления. (Более формально, операция обновления для данного ключа переносится -перед связью с любым (не нулевым) поиском для этого ключа, сообщающего об обновленном значении.) ".
Это означает, что происходит до из put
вызовите этот поток A
и последующий get
вызов в поток B
. Это, в свою очередь, означает, что:
-
get
найдет клавишу LongHolder
, а get
верните правильное значение.
Значение поля LongHolder
объекта *1052* также будет соответствовать ожидаемому, и даже если LongHolder
было бы изменяемым holder.
(Обратите внимание, что нет смысла объявлять неизменным LongHolder
здесь. Это эквивалентно java.lang.Long
... хотя и с более простым API.)
Можно ли переупорядочить операции таким образом, чтобы ссылка на LongHolder
из createValue
экранировала метод и, таким образом, стала доступна для других потоков перед тем, как поместить его в карту?
В основном нет. Соответствующие операции таковы:
LongHolder v = new LongHolder(i);
map.put(v, 1)
return v; // corrected typo
- Нет никакого изменения порядка исходного кода, который имеет какой-либо смысл.
- Если вы говорите о переупорядочении компилятором относительно потока, который работает
createValue
, JLS не разрешает это. Любое изменение порядка, которое изменяет видимость внутри потока, запрещено. Публикация v
через карту является безопасной публикацией благодаря свойствам get
и put
. (При реализации существуют барьеры памяти, которые запрещают вредные изменения порядка вызовов get
и put
.)
и 3. являются следствием происходит до отношений.
Теперь, если код должен был измениться на это:
LongHolder v = new LongHolder(i);
map.put(v, 1)
// Somehow modify v.i
return v;
затем , что будет формой небезопасной публикации. Поскольку изменение v.i
на A
происходит после put
, происходит до того, как соотношение между put
и get
в B
недостаточно для того, чтобы гарантировать новое значение v.i
видно в потоке B
. Это потому, что цепочка больше не работает.
Теперь я предполагаю, что если результат вызова createValue
в потоке A
был передан другому потоку (B
или C
) небезопасным способом, тогда последнему не гарантируется правильное значение для v.i
..., если LongHolder
является изменяемым. Но это не проблема с кодом createValue
/ doSmth
. И публикация значения v
через карту безопасна.
Но я считаю, что в этом обсуждении переупорядочений упущен смысл. Любое изменение порядка, которое нарушает семантику видимости, гарантированную моделью памяти, запрещено. JIT-компилятору не разрешено это делать. Так что вам просто нужно выполнить до анализа.