Как понять, что происходит, прежде чем последовательно

В главе 17 JLS он вводит концепцию: происходит, прежде чем согласованно.

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

В моем понимании это равносильно следующим словам: ... это тот случай, когда ни... ни...

Итак, мои первые два вопроса:

  • мое понимание верно?
  • что значит "wv = rv"?

Это также дает пример: 17.4.5-1

Thread 1 Thread 2

B = 1; A = 2; 

r2 = A; r1 = B; 

В первом порядке исполнения:

1: B = 1;

3: A = 2;

2: r2 = A;  // sees initial write of 0

4: r1 = B;  // sees initial write of 0

Сам заказ уже сказал нам, что два потока выполняются поочередно, поэтому мой третий вопрос: что означает оставленное число?

В моем понимании, причина того, что r2 и r 1 могут видеть начальную запись 0, состоит в том, что и A, и B не являются изменчивым полем. Итак, мой четвертый вопрос: правильно ли мое понимание?

Во втором порядке исполнения:

1: r2 = A;  // sees write of A = 2

3: r1 = B;  // sees write of B = 1

2: B = 1;

4: A = 2;

В соответствии с определением последовательности "происходит до", нетрудно понять, что порядок выполнения происходит до "согласованности" (если мое первое понимание верно). Итак, мой пятый и шестой вопросы: существует ли такая ситуация (читает, пишет, что происходит позже) в реальном мире? Если да, не могли бы вы привести реальный пример?

6 ответов

Решение

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

Более крайним примером является то, что код потока чтения оптимизирован с допущением, что, поскольку он никогда не изменяет значение, ему не нужно считывать его из памяти. В этом случае оптимизированный код никогда не видит изменения, выполненные другим потоком.

В обоих случаях использование volatile гарантирует, что чтение и запись происходят в согласованном порядке, и оба потока видят одно и то же значение. Иногда это описывается как всегда чтение из основной памяти, хотя это не обязательно так, потому что кэши могут напрямую общаться друг с другом. (Таким образом, производительность значительно меньше, чем вы могли ожидать)

Давайте посмотрим на определения в теории параллелизма:

Атомарность - это свойство операции, которое может быть выполнено полностью как единая транзакция и не может быть выполнено частично.

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

Упорядочивание - компилятор может изменять порядок операций / инструкций исходного кода для внесения некоторых оптимизаций.

Хороший пример atomicity является Compare and swap(CAS) реализация check then actпаттерн, который должен быть атомарным и позволять изменять переменную в многопоточном окружении. Вы можете написать свою собственную реализацию, еслиCTA вместе с volatile а также synchronaze или используя java.util.concurrent.atomic с sun.misc.Unsafe(выделение памяти, создание экземпляра без вызова конструктора) из Java 5 который использует JNI и преимущества процессора вместо volatile а также synchronaze

CASalgoritm имеет три параметра (A(адрес), O(старое значение), N(новое значение)). Если значение A == O, тогда поместите N в A, иначе O = значение из A и повторите эти действия снова

Происходит раньше

Официальный документ

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

volatile [About] в качестве примера

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

Давайте посмотрим на пример:

// Definitions
int a = 1;
int b = 2;
volatile boolean myVolatile = false;

// Thread A. Program order
{
    a = 5;
    b = 6;
    myVolatile = true; // <-- write
}

//Thread B. Program order
{
    Thread.sleep(1000); //just to show that writing into `myVolatile` was executed before

    System.out.println(myVolatile); // <-- read
    System.out.println(a);  //prints 5, not 1
    System.out.println(b);  //prints 6, not 2
}

Видимость - КогдаThread A изменения / запись летучий переменные также толкают все предыдущие изменения в оперативную память - Память в итоге все не летучие переменная будет актуальным и видимой для других потоков

Заказ:

  • Все операции перед записью в изменчивую переменную вThread A будет называться раньше. JVM может изменить их порядок, но гарантирует, что ни одна операция перед записью в изменчивую переменную вThread A будет вызываться после него.

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

Еще один хороший пример того, что случилось раньше, - это synchronizedмонитор [О себе]

Модель памяти Java определяет частичный порядок всех ваших действий в вашей программе, который называется " происходит раньше".
Чтобы гарантировать что нить Y способен видеть побочные эффекты действия X (не имеет значения, если X произошло в другом потоке или нет) X а также Y,
Если такого отношения нет, JVM может переупорядочить операции программы.
Теперь, если переменная является общей и доступной для многих потоков и записана (по крайней мере) одним потоком, если операции чтения до записи не упорядочены в соответствии с отношением " случается перед", то у вас есть гонка данных.
В правильной программе нет данных гонок.
Пример 2 темы A а также B синхронизируется на замке X,
Thread A приобретает блокировку (сейчас Thread B блокируется) и выполняет операции записи, а затем снимает блокировку X, Сейчас Thread B приобретает блокировку X и так как все действия Thread A были сделаны до снятия блокировки X они заказаны перед действиями Thread B который приобрел замок X после нити A (а также видно Thread B).
Обратите внимание, что это происходит при действиях, синхронизированных с одной и той же блокировкой. Не происходит, прежде чем отношения между потоками синхронизируются на разных блокировках

В сущности это правильно. Главное, на что следует обратить внимание: если вы не используете какую-либо форму синхронизации, нет гарантии, что чтение, которое происходит после записи в вашем программном порядке, увидит эффект этой записи, так как операторы могли быть перезаписаны.

существует ли такая ситуация (читает, видит, пишет, что происходит позже) в реальном мире? Если да, не могли бы вы привести реальный пример?

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

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

Q1: правильно ли я понимаю?

A: Да

Q2: что означает "wv = rv"?

A: Значение wv такое же, как и rv.

Q3: Что означает левый номер?

A: Я думаю, что это идентификатор оператора, как показано в «Таблице 17.4-A. Неожиданные результаты, вызванные переупорядочением операторов - исходный код». Но вы можете игнорировать это, потому что это не относится к условию «Другой порядок выполнения, который происходит, прежде чем согласованный:» Так что левое число - дерьмо полностью. Не придерживайтесь этого.

Q4: Насколько я понимаю, причина того, что как r2, так и r1 могут видеть начальную запись 0, - это оба поля A и B не являются изменчивыми. Итак, мой четвертый вопрос: правильно ли я понимаю?

A: Это одна из причин. повторный заказ тоже можно сделать. «Программа должна быть правильно синхронизирована, чтобы избежать противоречивого поведения, которое может наблюдаться при изменении порядка кода».

Q5 и 6: Во втором порядке выполнения ... Итак, мои пятый и шестой вопросы: существует ли такая ситуация (чтение, записи, которые происходят позже) в реальном мире? Если да, не могли бы вы привести мне реальный пример?

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

время 1: поток 2: A = 2

time 2: Thread 1: B = 1 // Без синхронизации здесь можно чередовать B = 1 потока 1

time 3: Thread 2: r1 = B // значение r1 равно 1

time 4: Thread 1: r2=A // значение r2 равно 2

Примечание: «Выполнение происходит - прежде, чем согласовано, если его набор действий происходит - перед согласованным»

Это означает, что если механизм синхронизации отсутствует, вы можете видеть счетчик воздействий интуитивно.

volatile int A = 0;
volatile int B = 0;
1: B = 1;
2: r2 = A; // r2 guarantees the value 1
3: A = 2;         
4: r1 = B; // r1 guarantees the value 2

Это потому, что переменные гарантируют, что происходит перед отношением. Если A и B не являются изменчивыми, система может изменить порядок оценки переменных, и это может стать противоинтуитивным.

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