Спецификация Java 11: 17.4.5. Порядок происходит-до (happens-before)

Два действия могут быть упорядочены отношениями "происходит-до". Если одно действие происходит раньше другого, то первое видимо и по порядку раньше второго.

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

  • Если x и y являются действиями одного и того же потока и x стоит перед y в программном порядке, тогда hb(x, y).
  • От конца конструктора объекта до начала финализатора (§12.6) для этого объекта существует граница "происходит-до".
  • Если действие x синхронизируется со следующим действием y, то у нас также есть hb(x, y).
  • Если hb(x, y) и hb(y, z), то hb(x, z).

Методы wait класса Object (§17.2.1) имеют связанные с ними действия блокировки и разблокировки; их отношения "происходит-до" определяются этими связанными действиями.

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

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

Более конкретно, если два действия имеют отношение "происходит-до", они не обязательно должны казаться выполненными в таком порядке для любого кода, с которым они не разделяют отношения "происходит-до". Записи в одном потоке, которые находятся в гонке данных с чтениями в другом потоке, могут, например, казаться неупорядоченными для этих чтений.

Отношение "происходит-до" определяет когда происходит гонка данных.

Набор краев синхронизации, S, является достаточным, если это минимальный набор, такой, что транзитивное замыкание S с программным порядком определяет все края "происходит-до" в исполнении. Этот набор уникален.

Из приведенных выше определений следует, что:

  • Разблокировка монитора "происходит-до" каждой последующей блокировки этого монитора.
  • Запись в volatile поле (§8.3.1.4) "происходит-до" каждым последующим чтением этого поля.
  • Вызов start() в потоке "происходит-до" любых действий в запущенном потоке.
  • Все действия в потоке "происходят-до" того, как какой-либо другой поток успешно вернется из join() в этом потоке.
  • Инициализация любого объекта по умолчанию происходит до любых других действий (кроме записи по умолчанию) программы.

Когда программа содержит два конфликтующих доступа (§17.4.1), которые не упорядочены отношением "происходит-до", говорят, что она содержит гонку данных.

Семантика операций, отличных от межпоточных действий, таких как чтение длин массивов (§10.7), выполнение проверенных приведений (§5.5, §15.16) и вызовы виртуальных методов (§15.12), напрямую не зависят от гонок данных.

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

Программа правильно синхронизируется тогда и только тогда, когда все последовательно согласованные исполнения свободны от гонок данных.

Если программа правильно синхронизирована, то все исполнения программы будут выглядеть последовательно согласованными (§17.4.3).

Это очень сильная гарантия для программистов. Программистам не нужно рассуждать о переупорядочивании, чтобы определить, что их код содержит гонки данных. Поэтому им не нужно рассуждать о переупорядочивании, чтобы определить, правильно ли синхронизирован их код. После определения того, что код синхронизирован правильно, программисту не нужно беспокоиться о том, что изменение порядка повлияет на его или ее код.

Программа должна быть правильно синхронизирована, чтобы избежать противоречивого поведения, которое может наблюдаться при изменении порядка кода. Использование правильной синхронизации не гарантирует правильного поведения программы в целом. Однако его использование действительно позволяет программисту рассуждать о возможном поведении программы простым способом; поведение правильно синхронизированной программы гораздо меньше зависит от возможных переупорядочений. Без правильной синхронизации возможно очень странное, сбивающее с толку и противоречащее интуиции поведение.

Мы говорим, что при чтении r переменной v разрешено наблюдать запись w в v, если в частичном "происходит-до" порядке выполнения прослеживается:

  • r не упорядочивается перед w (т.е. это не тот случай, когда hb(r, w)), и
  • нет промежуточной записи w' в v (т.е. нет записи w' в v так, чтобы hb(w, w') и hb(w', r)).

Неформально, чтению r разрешено видеть результат записи w, если не существует "происходит-до" порядка, чтобы предотвратить это чтение.

Набор действий A является согласованным, если для всех считываний r в A, где W(r) - это действие записи, наблюдаемое r, то не так, что либо hb(r, W (r)), либо что там существует запись w в A такая, что w.v = r.v и hb(W(r), w) и hb(w, r).

В последовательном наборе действий "происходит-до" того, как каждое чтение видит запись, которую ему разрешено видеть согласно порядку "происходит-до".

Пример - "происходит-до" согласованность

Для трассы в следующей таблице изначально A == B == 0. Трассировка может наблюдать r2 == 0 и r1 == 0 и по-прежнему быть "происходит-до" согласованной, поскольку существуют порядки выполнения, которые позволяют каждому чтению видеть соответствующие записи.

Таблица - поведение, допускаемое "происходит-до" согласованностью, но не последовательной согласованностью.

Поток 1 Поток 2
B = 1; A = 2;
r2 = A; r1 = B;

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

1: B = 1;
3: A = 2;
2: r2 = A;  // видит начальную запись 0
4: r1 = B;  // видит начальную запись 0

Другой порядок выполнения, чем "происходит-до" согласованность, следующий:

1: r2 = A;  // видит запись A = 2
3: r1 = B;  // видит запись B = 1
2: B = 1;
4: A = 2;

В этом исполнении операции чтения видят записи, которые происходят позже в порядке выполнения. Это может показаться нелогичным, но допустимо в соответствии с "происходит-до" согласованностью. Разрешение чтению видеть более поздние записи иногда может приводить к неприемлемому поведению.


Читайте также:


Комментарии

Популярные сообщения из этого блога

Методы класса Object в Java

Как получить текущий timestamp в Java

Основные опции JVM для повышения производительности и отладки