Ответ 1
Среда Spring не привязана к языку программирования Java, это всего лишь среда. Поэтому, как правило, необходимо пометить конечное поле non-, к которому обращаются разные потоки, чтобы оно было volatile
. В конце концов, bean-компонент Spring является не чем иным, как объектом Java, и применяются все языковые правила.
final
поля получают специальную обработку на языке программирования Java. Александр Шипилев, специалист по производительности Oracle, написал отличную статью по этому вопросу. Короче говоря, когда конструктор инициализирует final
поле, сборка для установки значения поля добавляет дополнительный барьер памяти, который обеспечивает правильное отображение поля любым потоком.
Для final
поля non- такой барьер памяти не создается. Таким образом, в общем, вполне возможно, что метод @PostConstruct
-annotated инициализирует поле, и это значение не видимо другим потоком, или, что еще хуже, видно, когда конструктор еще только частично выполнен.
Означает ли это, что вам всегда нужно помечать final
поля non- как энергозависимые?
Короче да. Если к полю могут обращаться разные потоки, вы делаете это. Не делайте ту же ошибку, что я сделал, когда думал только об этом в течение нескольких секунд (спасибо Jk1 за исправление) и мыслите с точки зрения последовательности выполнения вашего кода Java. Вы можете подумать, что контекст приложения Spring загружается в один поток. Это означает, что у загрузочного потока не будет проблем с изменчивым полем non-. Таким образом, вы можете подумать, что все в порядке, если вы не предоставляете контекст приложения другому потоку, пока он не будет полностью инициализирован, то есть вызван аннотированный метод. Думая так, вы могли бы предположить, что другие потоки не имеют возможности кэшировать неправильное значение поля, если вы не измените поле после этой начальной загрузки.
Напротив, скомпилированному коду разрешено переупорядочивать инструкции, т. @PostConstruct
Даже если метод @PostConstruct
-annotated вызывается до того, как связанный бин будет представлен другому потоку в вашем Java-коде, это отношение "до того" не обязательно сохраняется в скомпилированный код во время выполнения. Таким образом, другой поток может всегда считывать и кэшировать volatile
поле non-, пока оно еще не инициализировано вообще или даже частично инициализировано. Это может привести к незначительным ошибкам, и в документации Spring, к сожалению, не упоминается об этом. Такие детали JMM являются причиной, почему я лично предпочитаю final
поля и конструктор.
Обновление: Согласно этому ответу в другом вопросе, существуют сценарии, в которых отсутствие пометки поля как volatile
будет по-прежнему давать действительные результаты. Я исследовал это немного дальше, и среда Spring гарантирует, по сути, определенное количество событий, прежде чем безопасность из коробки. Взгляните на JLS в отношениях "происходит до", где четко говорится:
Разблокировка на мониторе происходит перед каждой последующей блокировкой на этом мониторе.
Среда Spring использует это. Все бины хранятся на одной карте, и Spring получает определенный монитор каждый раз, когда бин регистрируется или извлекается из этой карты. В результате тот же монитор разблокируется после регистрации полностью инициализированного компонента и блокируется перед извлечением этого компонента из другого потока. Это вынуждает этот другой поток уважать отношение "происходит до", которое отражается в порядке выполнения вашего Java-кода. Таким образом, если вы загрузите bean-компонент один раз, все потоки, которые обращаются к полностью инициализированному bean-компоненту, будут видеть это состояние до тех пор, пока они будут обращаться к bean-объекту каноническим способом (т.е. Явным извлечением путем запроса контекста приложения или автоматического переноса). Это делает, например, безопасным @PostConstruct
метода или использование метода @PostConstruct
даже без объявления поля volatile
. На самом деле вам следует избегать volatile
полей, так как они вводят накладные расходы времени выполнения для каждого чтения, что может быть болезненным при доступе к полю в циклах и потому, что ключевое слово сигнализирует о неправильном намерении. (Между прочим, насколько мне известно, среда Akka применяет аналогичную стратегию, в которой Akka, кроме Spring, выделяет некоторые аспекты проблемы.)
Эта гарантия, однако, предоставляется только для извлечения bean-компонента после его начальной загрузки. Если вы изменяете volatile
поле non- после его начальной загрузки или если вы теряете ссылку на компонент во время его инициализации, эта гарантия больше не применяется.
Проверьте эту старую запись в блоге, которая описывает эту функцию более подробно. По-видимому, эта функция не задокументирована, о чем знают даже пользователи Spring (но долгое время ничего не делали).