Может ли изменчивая переменная, которой никогда не присвоено значение null, когда-либо содержащее нуль?

Может в следующем концептуальном примере Java:

public class X implements Runnable {
    public volatile Object x = new Object();

    @Runnable
    public void run() {
        for (;;) {
            Thread.sleep(1000);
            x = new Object();
        }
    }
}

x когда-либо читается как null из другого потока?

Бонус: мне нужно объявить его изменчивым (мне все равно не нужно это значение, достаточно, чтобы когда-нибудь в будущем это будет новое назначенное значение и никогда не будет равно нулю)

Ответ 1

Технически, да, это возможно. Это основная причина оригинального ConcurrentHashMap readUnderLock. Javadoc даже объясняет, как:

Считывает поле значения записи под блокировкой. Вызывается, если значение поля всегда имеет значение null. Это возможно только в случае, если компилятор переупорядочивает инициализацию HashEntry с его назначением таблицы, что является законным по модели памяти, но, как известно, никогда не происходит.

Так как HashEntry value является изменчивым, этот тип переупорядочения является законным по подтверждению.

Мораль истории состоит в том, что все не финальные инициализации могут участвовать в гонке с конструкцией объекта.


Изменить: @Натан Хьюз задал правильный вопрос:

@John: в примере OP не была бы построена конструкция до того, как поток запускается в запущен? похоже, что после инициализации поля произойдет наложение "перед барьером".

У Doug Lea было пару комментариев по этой теме, весь поток может быть читать здесь. Он ответил на комментарий:

Но проблема заключается в том, должно ли происходить назначение нового экземпляра C в какую-то другую память после летучих хранилищ.

С ответом

Извините за неправильное запоминание того, почему я рассматривал эту проблему в основном: Если JVM всегда имеет предварительную нулевую память (что обычно не является хорошим вариантом), тогда даже если они явно не инициализированы, изменчивые поля должны быть обнулены в корпусе конструктора, с выпуском забора перед публикацией. И так, хотя есть случаи, когда JMM не строго требуется механика, препятствующая переупорядочению публикации в конструкторах классов с изменчивыми полями, единственное хорошее Варианты реализации для JVM - это использование нелетучих записей с заостренным затвором или для выполнения каждой изменчивой записи с полным ограждением. В любом случае, нет переупорядочения с публикацией. К сожалению, программисты не могут полагаться на спецификацию, чтобы гарантировать это, по крайней мере, до тех пор, пока JMM не будет пересмотрен.

И закончил с:

  • Программисты не ожидают, что даже если конечные поля безопасные публикации, неустойчивые поля не всегда так.

  • По различным причинам внедрения JVM организуют это волатильные поля в любом случае являются безопасными публикациями, по крайней мере, в случаев, о которых мы знаем.

  • Фактически обновление JMM/JLS для мандата это непросто (не малая настройка, которую я знаю). Но сейчас самое подходящее время для рассмотрения полной версии для JDK9.

  • В то же время было бы целесообразно дополнительно протестировать и утвердить JVM как отвечающие этой вероятной будущей спецификации.

Ответ 2

Это зависит от того, как публикуется экземпляр X.

Предположим, что X опубликовано неспроста, например. через поле не volatile

private X instance;
...
void someMethod() {
    instance = new X();
}

Другой поток, обращающийся к полю instance, может видеть ссылочное значение, относящееся к неинициализированному объекту X (т.е. его конструктор еще не запущен). В этом случае его поле X будет иметь значение null.

Приведенный выше пример означает

temporaryReferenceOnStack = new memory for X // a reference to the instance
temporaryReferenceOnStack.<init> // call constructor
instance = temporaryReferenceOnStack;

Но язык допускает следующее переупорядочение

temporaryReferenceOnStack = new memory for X // a reference to the instance
instance = temporaryReferenceOnStack;
temporaryReferenceOnStack.<init> // call constructor

или непосредственно

instance = new memory for X // a reference to the instance
instance.<init> // call constructor

В таком случае потоку разрешено видеть значение instance до того, как конструктор будет вызван для инициализации ссылочного объекта.

Теперь, насколько вероятно, что это произойдет в текущих JVM? Эх, я не мог придумать MCVE.


Бонус: мне нужно объявить его изменчивым (мне все равно, что это значение, достаточно, чтобы когда-нибудь в будущем это будет новое значение и никогда не равно null)

Публиковать защищающий объект безопасно. Или используйте поле final AtomicReference, которое вы set.

Ответ 3

Нет. Модель памяти Java гарантирует, что вы никогда не увидите x как null. x всегда должно быть начальным значением, которое оно было назначено, или некоторым последующим значением.

Это фактически работает с любой переменной, а не только с volatile. То, о чем вы просите, называется "из значений тонкого воздуха". C.F. Java Concurrency в Практике, которая рассказывает об этой концепции в некоторой степени.

Другая часть вашего вопроса "Нужно ли объявлять x как volatile:" учитывая контекст, да, он должен быть либо volatile, либо final.. Любой из них обеспечивает безопасную публикацию для вашего объекта, на который ссылается x. C.F. Безопасная публикация. Очевидно, что x не может быть изменено позже, если оно final.