Как понять, что происходит, прежде чем последовательно
В главе 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
CAS
algoritm имеет три параметра (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 не являются изменчивыми, система может изменить порядок оценки переменных, и это может стать противоинтуитивным.