Имеет ли пустое значение synchronized(this){} какое-либо значение для видимости памяти между потоками?

Я прочитал это в комментарии к StackOverflow, получившему одобрение:

Но если вы хотите быть в безопасности, вы можете добавить простой synchronized(this) {} в конце @PostConstruct [метод]

[обратите внимание, что переменные НЕ были изменчивыми]

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

Верна ли процитированная фраза? Сбрасывает ли пустой блок synchronized(this) {} все переменные, измененные в текущем методе, в «общую видимую» память?

Пожалуйста, рассмотрите несколько сценариев

  • что, если второй поток никогда не вызовет блокировку this? (предположим, что второй поток читает в других методах). Помните, что этот вопрос касается: переноса изменений в другие потоки, а не предоставления другим потокам возможности (синхронизированной) опросить изменения, сделанные исходным потоком. Также очень вероятно отсутствие синхронизации в других методах в контексте Spring @PostConstruct - как говорится в исходном комментарии.

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


person Piotr Müller    schedule 28.05.2014    source источник


Ответы (2)


Все записи, происходящие до monitor exit, видны всем потокам после monitor enter.

synchronized(this){} можно превратить в байт-код, например

monitorenter
monitorexit

Таким образом, если у вас есть куча операций записи до synchronized(this){}, они должны были произойти до monitorexit.

Это подводит нас к следующему пункту моего первого предложения.

виден всем тредам после monitor enter

Так что теперь, чтобы поток мог гарантировать, что записи произошли, он должен выполнить ту же самую синхронизацию, т.е. synchornized(this){}. Это по крайней мере выдаст monitorenter и установит ваши события перед заказом.


Итак, чтобы ответить на ваш вопрос

Сбрасывает ли пустой блок synchronized(this) {} все переменные, измененные в текущем методе, в «общую видимую» память?

Да, если вы поддерживаете ту же синхронизацию, когда хотите прочитать эти энергонезависимые переменные.

Чтобы ответить на ваши другие вопросы

что, если второй поток никогда не вызовет блокировку для этого? (предположим, что второй поток читает в других методах). Помните, что вопрос касается: сбросить изменения в другие потоки, а не дать другим потокам возможность (синхронизировать) опросить изменения, сделанные исходным потоком. Также очень вероятно отсутствие синхронизации в других методах в контексте Spring @PostConstruct.

Ну, в этом случае использование synchronized(this) без какого-либо другого контекста относительно бесполезно. Отношения «случается до того» не существуют, и теоретически они так же полезны, как и их отсутствие.

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

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

person John Vint    schedule 28.05.2014
comment
Но прав ли я, если скажу: когда второй поток вызывает этот метод ПЕРВЫЙ раз после изменений, он может получить устаревшее значение, потому что 100%-ное обновление происходит, когда второй поток выходит из этого метода (пустой синхронизированный последний в методе), поэтому изменения будут со 100% уверенностью распространяться только во ВТОРОМ вызове другого потока. я прав? - person Piotr Müller; 28.05.2014
comment
Ваше рассуждение верно. Таким образом, если synchornized находится в конце, как вы указываете, и другой поток входит в метод, любая из энергонезависимых переменных, безусловно, может быть устаревшей, пока этот поток не достигнет блока synchronized. - person John Vint; 28.05.2014
comment
При этом рассуждения также применимы к любому потоку, читающему эти энергонезависимые переменные без блока synchronized перед чтением (даже если не в методе @PostConstruct). - person John Vint; 28.05.2014

Многое из того, что написано об этом на SO, включая многие ответы/комментарии в этой теме, к сожалению, неверно.

Здесь применяется ключевое правило модели памяти Java: операция разблокировки на заданном мониторе происходит до последующей операции блокировки на том же мониторе. Если только один поток когда-либо получает блокировку, это не имеет смысла. Если виртуальная машина может доказать, что объект блокировки ограничен потоком, она может игнорировать любые ограничения, которые она могла бы создать в противном случае.

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

Подробнее о модели памяти Java см. в Параллелизм Java на практике, глава 16.

person Brian Goetz    schedule 30.05.2014