Запрещает ли барьер памяти StoreStore в Java переупорядочивание чтения-записи?
Теперь у нас есть
Load A
StoreStore
Store B
Возможно ли, что реальный порядок выполнения будет следующим
StoreStore
Store B
Load A
Если возможно, как объяснить ситуацию, которая, как представляется, нарушает The Java volatile Happens-Before Guarantee
.
Насколько мне известно, изменчивая семантика реализована с использованием следующей стратегии добавления барьера памяти JMM
insert a StoreStore before volatile variable write operation
insert a StoreLoad after volatile variable write operation
insert a LoadLoad after volatile variable read operation
insert a LoadStore after volatile variable read operation
Теперь, если у нас есть два потока Java, как показано ниже
поток 1
Load A
StoreStore
Store volatile B
поток 2
Load volatile B
Load C
Согласно "Неустойчивый случай Java - до гарантии",Load A
должно случиться раньше Load C
когда Load volatile B
после Store volatile B
, но если Load A
можно переупорядочить на "после сохранения летучих В", как гарантировать Load A is before Load C
?
2 ответа
С технической точки зрения язык Java не имеет барьеров для памяти. Скорее, модель памяти Java определяется в терминах " происходит до отношений"; подробнее см. ниже:
Обсуждаемая вами терминология взята из The JSR-133 Cookbook for Compiler Writers. Как говорится в документе, это руководство для людей, которые пишут компиляторы, реализующие модель памяти Java. Он интерпретирует значение JMM и явно не предназначен для использования в качестве официальной спецификации. JLS - это спецификация.
Раздел Руководства JSR-133, посвященный барьерам памяти, классифицирует их с точки зрения того, как они ограничивают определенные последовательности загрузок и сохранений. ЗаStoreStore
барьеры он говорит:
Последовательность:
Store1; StoreStore; Store2
гарантирует, что данные Store1 будут видны другим процессорам (т. е. сброшены в память) до данных, связанных с Store2 и всеми последующими инструкциями сохранения. В основном,StoreStore
На процессорах необходимы барьеры, которые иначе не гарантируют строгого упорядочивания сбросов из буферов записи и / или кешей в другие процессоры или в основную память.
Как видите, StoreStore
барьер только ограничивает поведение store
операции.
В вашем примере у вас есть load
за которым следует store
. СемантикаStoreStore
барьер ничего не говорит о load
операции. Таким образом, предлагаемый вами повторный заказ разрешен.
Это ответ только на обновленную часть вашего вопроса.
Прежде всего, представленный вами пример не является кодом Java. Поэтому мы не можем применять к нему рассуждения JMM. (Просто чтобы мы поняли это.)
Если вы хотите понять, как ведет себя Java-код, забудьте о барьерах памяти. Модель памяти Java сообщает вам все, что вам нужно сделать, чтобы операции чтения и записи в память имели гарантированное поведение. И все, что вам нужно знать, чтобы рассуждать о (правильном) поведении. Так:
- Напишите свой код Java
- Проанализируйте код, чтобы убедиться, что во всех случаях, когда потоку необходимо прочитать значение, записанное другим потоком, есть правильные события перед цепочками.
- Оставьте проблему компиляции вашего (правильного) кода Java для машинных инструкций компилятору.
Глядя на последовательности псевдо-инструкций в вашем примере, они не имеют особого смысла. Я не думаю, что настоящий компилятор Java будет (внутренне) использовать подобные барьеры при компиляции реального кода Java. Скорее, я думаю, было быStoreLoad
барьер памяти после каждой энергозависимой записи и перед каждым энергозависимым чтением.
Давайте рассмотрим несколько реальных фрагментов кода Java:
public int a;
public volatile int b;
// thread "one"
{
a = 1;
b = 2;
}
// thread "two"
{
if (b == 2) {
print(a);
}
}
Теперь, предполагая, что код в потоке "два" выполняется после потока "один", будет такая цепочка "происходит до":
a = 1
случается раньшеb = 2
b = 2
случается раньшеb == 2
b == 2
случается раньшеprint(a)
Если не задействован какой-либо другой код, цепочка "происходит до" означает, что поток "два" напечатает "1".
Заметка:
- Нет необходимости учитывать барьеры памяти, которые компилятор использует при компиляции кода.
- Барьеры зависят от реализации и являются внутренними по отношению к компилятору.
- Если вы посмотрите на собственный код, вы не увидите барьеров памяти как таковых. Вы увидите собственные инструкции, которые имеют необходимую семантику, чтобы гарантировать наличие (скрытого) барьера памяти.