Неустойчивые гарантии и исполнение вне порядка

ВАЖНОЕ ИЗМЕНЕНИЕ Я знаю, что «происходит до» в streamе, где выполняются два назначения, мой вопрос: возможно ли, чтобы другой stream читал «b» ненулевой, а «a» по-прежнему равен нулю. Поэтому я знаю, что если вы вызываете doIt () из того же streamа, что и тот, где вы ранее называли setBothNonNull (…), тогда он не может вызывать исключение NullPointerException. Но что, если вы вызываете doIt () из другого streamа, чем тот, который вызывает setBothNonNull (…) ?

Обратите внимание, что этот вопрос касается только ключевого слова volatile и volatile гарантий: речь идет не о synchronized ключевом слове (поэтому, пожалуйста, не отвечайте «вы должны использовать синхронизацию», потому что у меня нет проблемы: я просто хочу понимать volatile гарантии (или отсутствие гарантий) в отношении исполнения вне порядка).

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

 a = one; b = two; 

Вышеприведенные утверждения не будут перенаправлены, и все streamи будут наблюдать значение one для a если b уже имеет значение two .

Вот нить, в которой Дэвид Холмс объясняет это:
http://markmail.org/message/j7omtqqh6ypwshfv#query:+page:1+mid:34dnnukruu23ywzy+state:results

EDIT (ответ на последующие действия): то, что говорит Холмс, компилятор теоретически может сделать переупорядочение, если есть только stream A. Однако существуют другие streamи, и они МОГУТ обнаружить переупорядочение. Вот почему компилятору НЕ разрешено выполнять это переупорядочение. Для модели java-памяти требуется, чтобы компилятор специально удостоверился, что ни один stream никогда не обнаружит такое переупорядочение.

Но что, если вы вызываете doIt () из другого streamа, чем тот, который вызывает setBothNonNull (…)?

Нет, у вас все равно НИКОГДА не будет NPE. volatile семантика навязывают межпоточное упорядочение. Это означает, что для всех существующих streamов назначение one происходит до назначения two .

Правильно ли я понимаю, что из-за нестандартного исполнения я могу получить исключение NullPointerException? Другими словами: нет гарантии, что, поскольку я прочитал ненулевой «b», я прочитаю ненулевой «a»?

Предполагая, что значения, присвоенные a и b или непустые, я думаю, ваше понимание неверно . JLS говорит об этом:

( 1 ) Если x и y – действия одного и того же streamа, а 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 любым streamом (где последующее определяется в соответствии с порядком синхронизации).

теорема

Учитывая, что stream # 1 вызвал setBoth(...); один раз и что аргументы не равны нулю, и что stream # 2 заметил, что b не имеет значения null, тогда stream # 2 не может затем наблюдать, что a является нулевым.

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

  1. По ( 1 ) – hb (write (a, non-null), write (b, non-null)) в streamе # 1
  2. По ( 2 ) и ( 4 ) – hb (write (b, non-null), read (b, non-null))
  3. По ( 1 ) – hb (read (b, non-null), read (a, XXX)) в streamе # 2,
  4. По ( 4 ) – hb (write (a, non-null), read (b, non-null))
  5. По ( 4 ) – hb (write (a, non-null), read (a, XXX))

Другими словами, запись ненулевого значения в «случится раньше» считывает значение (XXX) of. Единственный способ, которым XXX может быть нулевым, – это то, что было какое-то другое действие, записывающее null в a такое, что hb (write (a, non-null), write (a, XXX)) и hb (write (a, XXX) прочитал (а, XXX)). И это невозможно в соответствии с определением проблемы, и поэтому XXX не может быть нулевым. QED.

Объяснение – JLS заявляет, что отношение hb (…) (“произойдет-до”) не полностью запрещает переупорядочение. Однако, если hb (xx, yy), то переупорядочение действий xx и yy разрешено только в том случае, если полученный код имеет тот же наблюдаемый эффект, что и исходная последовательность.

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

Хотя Стивен C и принятые ответы хороши и в значительной степени охватывают его, стоит обратить внимание на то, что переменная a не должна быть неустойчивой – и вы все равно не получите NPE. Это связано с тем, что произойдет связь между a = one и b = two , независимо от того, a ли она volatile . Таким образом, формальное доказательство Стивена C по-прежнему применяется, просто не нужно быть неустойчивым.

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

 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 .

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

  • Необходимость волатильного модификатора в двойной проверенной блокировке в .NET.
  • Volatile boolean vs AtomicBoolean
  • Interesting Posts

    Определить информацию о процессе программно в Darwin / OSX

    Список и подсчет уникальных слов из документа Word

    Синхронизированный аудиовыход на нескольких компьютерах? VLC? Аппаратные решения?

    Как работает getchar ()?

    Как получить доступ к методу Java в приложении на C ++

    Используйте DSL-модем / беспроводной маршрутизатор в качестве немого беспроводного маршрутизатора

    Как установить .NET framework как необходимое условие с помощью InnoSetup?

    Windows 7 не может найти драйвер – деактивирует клавиатуру и мышь

    Что может вызвать это изменение в ping?

    Как поместить комментарий линии для многострочной команды

    Изменение видимого нижнего колонтитула на страницах каталога Apache

    Как запустить mvim (MacVim) из терминала?

    Поведение sizeof для массивов переменной длины (только C)

    Непонимание понимания групп и обратных ссылок

    Какие разделы можно удалить на ноутбуке HP6550b?

    Давайте будем гением компьютера.