Энергозависимые гарантии и выполнение с изменением последовательности [дубликат]

Этот вопрос уже имеет ответ здесь:

ВАЖНОЕ РЕДАКТИРОВАНИЕ, которое я знаю о, "происходит, прежде" в потоке, где эти два присвоения происходят, мой вопрос, был бы это быть возможным для другого потока считать "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, для не решения проблемы).

41
задан Gray 19 July 2012 в 17:19
поделиться

4 ответа

Нет, вы никогда не получите 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.

25
ответ дан 27 November 2019 в 00:55
поделиться

Правильно ли я понимаю, что из-за неупорядоченного выполнения я могу получить исключение 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.

Неофициальное доказательство

  1. Автор ( 1 ) - hb (запись (a, ненулевое значение), запись (b, ненулевое значение)) в потоке №1
  2. Автор ( 2 ) и ( 4 ) - hb (запись (b, ненулевое значение), чтение (b, ненулевое значение))
  3. Автор ( 1 ) - hb (читать (b, ненулевое), читать (a, XXX)) в потоке № 2,
  4. By ( 4 ) - hb (писать (a, ненулевое), читать (b, ненулевое))
  5. By ( 4 ) - hb (write (a, non-null), read (a, XXX))

Другими словами, запись ненулевое значение для «происходит до» чтения значения (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 разрешено только , если результирующий код имеет тот же наблюдаемый эффект, что и исходная последовательность.

8
ответ дан 27 November 2019 в 00:55
поделиться

Я прочитал эту страницу и нашел энергонезависимую и несинхронизированную версию вашего вопроса:

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 .

(Я понимаю, что это не отвечает на ваш вопрос, но дополняет его.)

0
ответ дан 27 November 2019 в 00:55
поделиться

Я нашел следующее сообщение, в котором объясняется, что volatile имеет ту же семантику упорядочения, что и synchronized в этом случае. Java Volatile - мощный инструмент

2
ответ дан 27 November 2019 в 00:55
поделиться
Другие вопросы по тегам:

Похожие вопросы: