Этот вопрос уже имеет ответ здесь:
ВАЖНОЕ РЕДАКТИРОВАНИЕ, которое я знаю о, "происходит, прежде" в потоке, где эти два присвоения происходят, мой вопрос, был бы это быть возможным для другого потока считать "b" непустой указатель, в то время как "a" является все еще пустым. Таким образом, я знаю, что при вызове мелкой монеты () от того же потока как тот, где Вы ранее назвали setBothNonNull (...) затем, он не может бросить NullPointerException. Но что, если Вы называете мелкую монету () от другого потока, чем тот, звоня setBothNonNull (...)?
Обратите внимание, что этот вопрос только о volatile
ключевое слово и volatile
гарантии: это не о synchronized
ключевое слово (поэтому не отвечайте, что "необходимо использовать, синхронизируются", поскольку у меня нет проблемы для решения: Я просто хочу понять volatile
гарантии (или отсутствие гарантий) относительно выполнения с изменением последовательности).
Скажите, что у нас есть объект, содержащий два volatile
Строковые ссылки, которые инициализируются к пустому указателю конструктором и что у нас есть только один способ изменить эти две Строки: путем вызова setBoth (...) и что мы можем только установить их ссылки впоследствии на ненулевую ссылку (только конструктору разрешают установить их в NULL).
Например (это - просто пример, еще нет никакого вопроса):
public class SO {
private volatile String a;
private volatile String b;
public SO() {
a = null;
b = null;
}
public void setBothNonNull( @NotNull final String one, @NotNull final String two ) {
a = one;
b = two;
}
public String getA() {
return a;
}
public String getB() {
return b;
}
}
В setBothNoNull (...), строка, присваивающая непустой параметр "a", появляется перед строкой, присваивающей непустой параметр "b".
Затем, если я делаю это (еще раз, нет никакого вопроса, вопрос прибывает затем):
doIt() {
if ( so.getB() != null ) {
System.out.println( so.getA().length );
}
}
Я корректен в своем понимании, что из-за выполнения с изменением последовательности я могу получить NullPointerException?
Другими словами: нет никакой гарантии, что, потому что я считал непустой указатель "b", я считаю непустой указатель "a"?
Поскольку из-за неисправного (много) процессора и пути volatile
работы "b" могли быть поручены прежде "a"?
volatile
гарантии, который читает последующий за записью, должны всегда видеть последнее записанное значение, но здесь существует неисправное право "проблемы"? (еще раз "проблема" сделана нарочно попытаться понять семантику volatile
ключевое слово и Модель памяти Java, для не решения проблемы).
Нет, вы никогда не получите NPE. Это происходит потому, что volatile
также имеет эффект памяти - вводит отношения happens-before. Другими словами, он предотвращает переупорядочивание
a = one;
b = two;
Утверждения, приведенные выше, не будут переупорядочены, и все потоки будут наблюдать значение один
для a
, если b
уже имеет значение два
.
Вот тема, в которой Дэвид Холмс объясняет это:
http://markmail.org/message/j7omtqqh6ypwshfv#query:+page:1+mid:34dnnukruu23ywzy+state:results
EDIT (ответ на продолжение): Холмс хочет сказать, что компилятор мог бы теоретически сделать переупорядочение, если бы существовал только поток A. Однако существуют другие потоки, и они могут обнаружить переупорядочение. Вот почему компилятору НЕ разрешается делать такое переупорядочивание. Модель памяти java требует от компилятора специально убедиться, что ни один поток никогда не обнаружит такого переупорядочивания.
Но что, если кто-то вызывает doIt() из из другого потока, чем тот, который вызывает setBothNonNull(...) ?
Нет, у вас все равно НИКОГДА не будет NPE. семантика volatile
действительно накладывает межпотоковое упорядочивание. Это означает, что для всех существующих потоков присвоение one
происходит раньше присвоения two
.
Правильно ли я понимаю, что из-за неупорядоченного выполнения я могу получить исключение NullPointerException? Другими словами: нет никакой гарантии, что, поскольку я прочитал ненулевое «b», я прочту ненулевое «a»?
Предполагая, что значения, присвоенные a
и b
или ненулевое значение, я думаю, ваше понимание неверно . JLS говорит следующее:
( 1 ) Если x и y являются действиями одного и того же потока и x стоит перед y в программном порядке, тогда hb (x, y).
( 2 ) Если действие x синхронизируется со следующим действием y, то у нас также есть hb (x, y).
( 3 ) Если hb (x, y) и hb (y, z), то hb (x, z).
и
( 4 ) Запись в изменчивую переменную (§8.3.1.4)
v
синхронизируется - со всеми последующими чтениямиv
любой поток (где последующий определяется в соответствии с порядком синхронизации).
Теорема
Учитывая, что поток №1 вызывал
setBoth (...);
один раз, и что аргументы не равны нулю, и что поток №2 наблюдалb
, чтобы быть ненулевым, тогда поток № 2 не может наблюдатьa
как null.
Неофициальное доказательство
Другими словами, запись ненулевое значение для
«происходит до» чтения значения (XXX) из a
. Единственный способ, которым XXX может быть null, - это если было какое-то другое действие, записывающее null в a
, такое, что hb (write (a, non-null), write (a, XXX)) и hb (write (a, XXX), читать (a, XXX)). А это невозможно согласно определению проблемы, и поэтому XXX не может быть нулевым. QED.
Объяснение - JLS утверждает, что отношение hb (...) («происходит до») не запрещает полностью переупорядочивание. Однако, если hb (xx, yy), то переупорядочение действий xx и yy разрешено только , если результирующий код имеет тот же наблюдаемый эффект, что и исходная последовательность.
Я прочитал эту страницу и нашел энергонезависимую и несинхронизированную версию вашего вопроса:
class Simple {
int a = 1, b = 2;
void to() {
a = 3;
b = 4;
}
void fro() {
System.out.println("a= " + a + ", b=" + b);
}
}
для
может получить 1 или 3 для значения a
и независимо может получить 2 или 4 для значения b
.
(Я понимаю, что это не отвечает на ваш вопрос, но дополняет его.)
Я нашел следующее сообщение, в котором объясняется, что volatile имеет ту же семантику упорядочения, что и synchronized в этом случае. Java Volatile - мощный инструмент