Переупорядочивание инструкций и отношения "до и после" в Java

В книге "Параллелизм на практике на Java" нам несколько раз говорили, что инструкции нашей программы могут быть переупорядочены либо компилятором, либо JVM во время выполнения, либо даже процессором. Таким образом, мы должны предположить, что исполняемая программа не будет выполнять свои инструкции точно в том же порядке, что мы указали в исходном коде.

Тем не менее, в последней главе, обсуждающей модель памяти Java, приведен список правил " до и после", указывающий, какой порядок команд сохраняется JVM. Первое из этих правил:

  • "Правило программного порядка. Каждое действие в потоке происходит перед каждым действием в этом потоке, которое происходит позже в программном порядке".

Я считаю, что "порядок программ" относится к исходному коду.

Мой вопрос: исходя из этого правила, мне интересно, какую инструкцию можно на самом деле изменить.

"Действие" определяется следующим образом:

Модель памяти Java задается в терминах действий, которые включают чтение и запись в переменные, блокировки и разблокировки мониторов, а также запуск и соединение с потоками. JMM определяет частичное упорядочение, которое вызывается раньше, для всех действий в программе. Чтобы гарантировать, что поток, выполняющий действие B, может видеть результаты действия A (независимо от того, встречаются ли A и B в разных потоках), должно существовать событие до того, как отношения между A и B. В отсутствие события происходит до упорядочения между двумя Операции, JVM может свободно переупорядочивать их по своему усмотрению.

Другие упомянутые правила заказа:

  • Правило блокировки монитора. Разблокировка блокировки монитора происходит перед каждой последующей блокировкой той же блокировки монитора.
  • Правило изменчивой переменной. Запись в энергозависимое поле происходит перед каждым последующим чтением этого же поля.
  • Правило начала потока. Вызов Thread.start в потоке происходит перед каждым действием в запущенном потоке.
  • Правило завершения потока. Любое действие в потоке происходит до того, как какой-либо другой поток обнаружит, что поток завершен, либо путем успешного возврата из Thread.join, либо с помощью Thread.isAlive, возвращающего false.
  • Правило прерывания. Поток, вызывающий прерывание в другом потоке, происходит до того, как прерванный поток обнаружит прерывание (либо сгенерировав InterruptedException, либо вызвав isInterrupted или interrupted).
  • Правило финализатора. Конец конструктора для объекта происходит до начала финализатора для этого объекта.
  • Транзитивность. Если A происходит до B, а B происходит до C, то A происходит до C.

1 ответ

Решение

Ключевой момент правила программного порядка: в потоке.

Представьте себе эту простую программу (все переменные изначально 0):

T1:

x = 5;
y = 6;

T2:

if (y == 6) System.out.println(x);

С точки зрения T1, выполнение должно соответствовать назначению y после x (программный порядок). Однако с точки зрения T2 это не должно быть так, и T2 может вывести 0.

T1 фактически разрешено присваивать y первым, так как 2 присваивания независимы, и их замена не влияет на выполнение T1.

При правильной синхронизации T2 всегда печатает 5 или ничего.

РЕДАКТИРОВАТЬ

Вы, кажется, неправильно истолковываете значение программного порядка. Правило заказа программы сводится к:

Если x а также y действия того же потока и x приходит раньше y в программном порядке, то hb(x, y) (т.е. x случается, перед темy).

Происшествие имеет очень специфическое значение в JMM. В частности, это не значит, что y=6 должен следовать за x=5 в T1 с точки зрения настенных часов. Это только означает, что последовательность действий, выполняемых T1, должна соответствовать этому порядку. Вы также можете обратиться к JLS 17.4.5:

Следует отметить, что наличие отношения "происходит до" между двумя действиями не обязательно означает, что они должны происходить в том порядке в реализации. Если изменение порядка дает результаты, соответствующие юридическому исполнению, это не является незаконным.

В приведенном выше примере вы согласитесь, что с точки зрения T1 (то есть в однопоточной программе), x=5;y=6; согласуется с y=6;x=5; так как вы не читаете значения. Оператор в следующей строке гарантированно в T1 видит эти 2 действия независимо от порядка их выполнения.

Другие вопросы по тегам