РЕДАКТИРОВАТЬ: Важное обновление благодаря @ irreputable.
Если объект не скрылся во время строительства (см. Ниже), присваивание mMyInt=1
происходит перед любым доступом к получателю / установщику. Также в Java назначение объектов является атомарным (есть вероятность того, что вы заметите какой-то неверный назначенный адрес. Будьте осторожны, потому что 64-битные примитивные назначения, такие как double
и long
, не являются атомарными).
Таким образом, в этом случае возможное значение равно 1 или 2.
Объект может убежать во время строительства в такой ситуации:
class Escape {
Integer mmyInt = 1;
Escape(){
new Thread(){
public void run(){
System.out.println(Escape.this.mmyInt);
}
}.start();
}
}
Хотя на практике это, вероятно, случается редко, в вышеупомянутом случае новый поток может наблюдать не полностью построенный объект Escape
и, таким образом, в теории получить значение mmyInt
null
(AFAIK вы все равно не будете получить некоторую случайную ячейку памяти).
Что если это HashMap
объект? Переменная экземпляра mMyMap
имеет первоначальное значение. Затем первый
поток вызывает "mMyMap = new HashMap ();"
Второй поток вызывает "возврат
mMyMap; "Может ли второй поток получить
null, или он может быть только оригинальным или
новый объект HashMap?
Когда «Назначение ссылки на объект является атомарным», это означает, что вы НЕ будете наблюдать промежуточное назначение. Это либо значение до, либо значение после. Таким образом, если единственное присвоение, которое происходит, это map = someNonNullMap();
после завершения строительства (и поле было присвоено ненулевое значение во время строительства), и объект не избежал во время строительства, вы не сможете наблюдать null
.
Обновление:
Я проконсультировался с экспертом по параллелизму, и, по его словам, модель памяти Java позволяет компиляторам переупорядочивать присваивания и конструирование объектов (хотя на практике я полагаю, что это крайне маловероятно).
Так, например, в приведенном ниже случае thread1 может выделить некоторую кучу, присвоить некоторое значение map
, продолжение строительства map
. Тем временем поток 2 приходит и наблюдает частично построенный объект.
class Clever {
Map map;
Map getMap(){
if(map==null){
map = deriveMap(); }
return map;
}
}
JDK имеет аналогичную конструкцию в классе String
(не точная цитата):
class String {
int hashCode = 0;
public int hashCode(){
if(hashCode==0){
hashCode = deriveHashCode();
}
return hashCode;
}
}
Это работает, потому что, по мнению тех же экспертов по параллелизму, энергонезависимый кэш является примитивным, а не объектом.
Этих проблем можно избежать, если ввести отношения до того, как они возникнут. В вышеупомянутых случаях это можно сделать, объявив членов volatile
. Также для 64-битных примитивов их объявление volatile
сделает их назначение атомарным.