Ответ 1
Это называется эффектом "преждевременной публикации".
Положив это просто, JVM разрешено изменять порядок инструкций программы (по соображениям производительности), если такое переупорядочение не нарушает ограничений JMM.
Вы ожидаете, что код f = new FinalFieldExample();
будет запущен следующим образом:
1. создайте экземпляр FinalFieldExample
2. назначить 3 на x
3. назначьте 4 к y
4. назначить созданный объект переменной f
Но в предоставленном коде ничто не может остановить JVM из переупорядочения команд, поэтому он может запускать код следующим образом:
1. создайте экземпляр FinalFieldExample
2. назначить 3 на x
3. назначить исходный, не полностью инициализированный объект переменной f
4. назначьте 4 к y
Если переупорядочение происходит в среде с одним потоком, мы его даже не заметим. Это потому, что мы ожидаем, что объекты будут полностью созданы до того, как мы начнем работать с ними, и JVM уважает наши ожидания. Теперь, что может случиться, если несколько потоков одновременно запускают этот код? В следующем примере Thread1 выполняет метод writer()
и Thread2 - метод reader()
:
Тема 1: создать экземпляр FinalFieldExample
Тема 1: назначить 3 на x
Тема 1: присвоить исходный, не полностью инициализированный объект переменной f
Тема 2: чтение f
, это не пусто
Тема 2: чтение f.x, это 3
Тема 2: чтение f.y, все равно 0
Тема 1: назначить 4 на y
Определенно не хорошо. Чтобы не допустить JVM, нам нужно предоставить дополнительную информацию о программе. В этом конкретном примере существуют некоторые способы исправления последовательности:
- объявить
y
как переменнуюfinal
. Это вызовет эффект freeze". Короче говоря, конечные переменные всегда будут инициализироваться в тот момент, когда вы обращаетесь к ним, если ссылка на объект не была пропущена во время строительства. - объявить
f
как переменнуюvolatile
. Это создаст " порядок синхронизации и устранит проблему. Короче говоря, инструкции не могут быть переупорядочены ниже волатильной записи и выше изменчивого чтения. Присвоение переменнойf
- это volatile write, что означает, что инструкцииnew FinalFieldExample()
не могут быть переупорядочены и выполнены после назначения. Чтение из переменнойf
является изменчивым чтением, поэтому чтениеf.x
невозможно выполнить перед ним. Комбинация v-write и v-read называется порядком синхронизации и обеспечивает желаемую согласованность памяти.
Здесь - хороший блог, который может ответить на все ваши вопросы о JMM.