После прочтения большего количества блогов/статей и т.д., я теперь действительно смущен о поведении загрузки/хранения до/после барьера памяти.
Ниже приводятся 2 цитаты из Дуга Ли в одной из его разъяснительной статьи о JMM, которые оба очень тяжелы:
- Все, что было видимо для потока A, когда оно записывает в volatile field f, становится видимым для потока B, когда он читает f.
- Обратите внимание, что важно, чтобы оба потока обращались к одной и той же изменчивой переменной, чтобы правильно настроить связь между событиями. Дело не в том, что все, что видимо для потока A, когда оно записывает изменчивое поле f, становится видимым для потока B после того, как оно считывает изменчивое поле g.
Но тогда, когда я просмотрел еще один blog об уровне памяти, я получил следующее:
- Указатель "sfence" на x86 хранит барьер, заставляя все инструкции хранить перед барьером, который должен произойти перед барьером, и буферы хранилища сброшены в кеш для процессора, на котором он выдается.
- Нагрузочный барьер, инструкция "lfence" на x86, заставляет все инструкции по загрузке после того, как барьер должен произойти после барьера, а затем подождите, пока буфер загрузки будет стекать для этого CPU.
Для меня разъяснение Дуга Ли более строгое, чем другое: в основном, это означает, что если барьер нагрузки и барьер хранилища находятся на разных мониторах, согласованность данных не будет гарантирована. Но более поздний означает, что даже если барьеры находятся на разных мониторах, будет гарантирована согласованность данных. Я не уверен, правильно ли я понимаю эти 2, и также я не уверен, кто из них прав.
Учитывая следующие коды:
public class MemoryBarrier {
volatile int i = 1, j = 2;
int x;
public void write() {
x = 14; //W01
i = 3; //W02
}
public void read1() {
if (i == 3) { //R11
if (x == 14) //R12
System.out.println("Foo");
else
System.out.println("Bar");
}
}
public void read2() {
if (j == 2) { //R21
if (x == 14) //R22
System.out.println("Foo");
else
System.out.println("Bar");
}
}
}
Скажем, у нас есть 1 поток записи TW1, сначала вызываем метод writeB (запись) MemoryBarrier, тогда у нас есть 2 потока чтения TR1 и TR2, вызывающие метод MemoryBarrier read1() и read2(). Рассматривайте эту программу на CPU, который не сохраняет (x86 DO сохраняет порядок для таких случаев, что не так), в соответствии с моделью памяти будет существовать барьер StoreStore (скажем, SB1) между W01/W02, а также 2 барьер LoadLoad между R11/R12 и R21/R22 (скажем RB1 и RB2).
- Поскольку SB1 и RB1 находятся на одном мониторе i, поэтому поток TR1, который вызывает read1, должен всегда видеть 14 на x, также всегда печатается "Foo".
- SB1 и RB2 находятся на разных мониторах, если Doug Lea верен, нить TR2 не будет гарантированно видеть 14 на x, что означает, что "Bar" может печататься иногда. Но если барьер памяти работает, как Мартин Томпсон, описанный в blog, барьер Store будет выталкивать все данные в основную память, а барьер нагрузки будет вытаскивать все данные из основной памяти в cache/buffer, тогда TR2 также будет гарантированно видеть 14 на x.
Я не уверен, какой из них правильный, или оба они но то, о чем говорил Мартин Томпсон, только для архитектуры x86. JMM не гарантирует, что изменение на x видимо для TR2, но реализация x86 делает.
Спасибо ~