введение
В предыдущей статье говорилось о модели памяти Java, в которой мы сказали, что JMM основан на принципе «происходит до».
Почему ты это сказал? Потому что во время выполнения Java-программы компилятор и процессор выполняют серию оптимизаций кода, который мы пишем, чтобы повысить эффективность выполнения программы. Это включает в себя «переупорядочивание» инструкций.
Изменение порядка приводит к тому, что наш код не выполняется в том порядке, в котором код был написан, поэтому наши результаты не путаются после выполнения программы, причина в том, что модель памяти Java следует принципу «происходит до». В соответствии с правилом «происходит до того, как» независимо от того, как будет переупорядочена программа, результат выполнения не изменится, поэтому мы не увидим искажения результата программы.
изменение порядка
Что такое переупорядочить? С точки зрения непрофессионала, компилятор и процессор вносят определенные изменения в порядок выполнения инструкций, чтобы оптимизировать производительность выполнения программы.
Переупорядочивание происходит на различных этапах выполнения программы, включая сброс компилятора, параллельный сброс на уровне инструкций и переупорядочивание системы памяти. Мы не анализируем здесь конкретно каждый процесс переупорядочения, если знаем, что переупорядочение приводит к тому, что наш код не выполняется в том порядке, в котором мы его написали.
После того, как переупорядочение происходит во время выполнения одного потока, мы не можем его воспринять, как показано в следующем коде:
int a = 1; //步骤1
int b = 2; //步骤2
int c = a + b; //步骤3
Изменение порядка 1 и 2 не повлияет на результат выполнения программы.В некоторых случаях 1 и 2 могут быть изменены для оптимизации производительности. Изменение порядка 2 и 3 влияет на результат выполнения, поэтому компилятор и процессор не будут изменять порядок 2 и 3.
В многопоточности при неправильной синхронизации возникновение переупорядочения мы можем воспринять, например, в следующем коде:
public class AAndB {
int x = 0;
int y = 0;
int a = 0;
int b = 0;
public void awrite() {
a = 1;
x = b;
}
public void bwrite() {
b = 1;
y = a;
}
}
public class AThread extends Thread{
private AAndB aAndB;
public AThread(AAndB aAndB) {
this.aAndB = aAndB;
}
@Override
public void run() {
super.run();
this.aAndB.awrite();
}
}
public class BThread extends Thread{
private AAndB aAndB;
public BThread(AAndB aAndB) {
this.aAndB = aAndB;
}
@Override
public void run() {
super.run();
this.aAndB.bwrite();
}
}
private static void testReSort() throws InterruptedException {
AAndB aAndB = new AAndB();
for (int i = 0; i < 10000; i++) {
AThread aThread = new AThread(aAndB);
BThread bThread = new BThread(aAndB);
aThread.start();
bThread.start();
aThread.join();
bThread.join();
if (aAndB.x == 0 && aAndB.y == 0) {
System.out.println("resort");
}
aAndB.x = aAndB.y = aAndB.a = aAndB.b = 0;
}
System.out.println("end");
}
Без переупорядочивания есть четыре возможности порядка выполнения программы:
Но программа будет печатать «resort» после многократного выполнения, что означает, что и поток A, и поток B переупорядочиваются.определение «происходит до»
«случается-прежде» определяет восемь правил, каждое из которых используется для того, чтобы гарантировать, что если А происходит раньше В, то результат выполнения А виден В, а порядок выполнения А предшествует В.
- Правило порядка выполнения программы: В отдельном потоке, в соответствии с последовательностью выполнения программного кода, операция, выполняемая первой (по времени), происходит раньше (по времени), а операция выполняется после.
- Управление правилами блокировки: операция разблокировки происходит до (последовательность во времени, то же самое ниже) операции блокировки той же блокировки.
- Правила для изменчивых переменных: Запись в изменчивую переменную, работающую за операцией «происходит до чтения» переменных.
- Правила запуска потока: Метод Start () объекта Thread Произойдет перед этим действием.
- Правила завершения потока: все операции потока происходят до того, как будет обнаружено завершение потока, и можно определить, что поток завершил выполнение с помощью конца метода Thread.join() и возвращаемого значения Thread. .является живым().
- Правила прерывания потока: вызов метода прерывания() потока происходит до того, как произойдет событие, когда код прерванного потока обнаружит прерывание.
- Правила финализации объекта: Инициализация объекта (окончание выполнения конструктора) происходит до запуска его метода finalize().
- Транзитивность: если операция А происходит до операции В, а операция В происходит до операции С, то можно сделать вывод, что А происходит до операции С.
«случается-прежде» определяет так много правил, которые можно суммировать в одном предложении: правила «события-прежде» гарантируют, что результаты выполнения однопоточных и правильно синхронизированных многопоточных операций не будут изменены.
Зачем правила последовательности программы обеспечения, многопоточное выполнение вышеупомянутого или появление переупорядочения его? Это потому, что только происходит - до правила - обеспечить, чтобы модель памяти Java сделала программист. В однопоточной резьбоке ему не волнует порядок выполнения программы, чтобы обеспечить, чтобы только один резьбовые результаты выполнения в рамках программы должны быть правильными, модель памяти Java позволяет компилятору и процессору в произошедших правилам Переупорядочение выполнения программы.
И с точки зрения программиста важно не то, действительно ли переупорядочены две операции, а то, изменился ли результат выполнения программы.
Вышеупомянутая программа не синхронизирует несколько потоков, когда один поток будет переупорядочен, что приводит к неожиданным результатам.
как-будто-серийная семантика
Объяснено в The Art of Java Concurrent Programming:
Как-будто-последовательный означает, что независимо от того, насколько переупорядочены (компилятор и процессор для улучшения параллелизма), результат выполнения (однопоточной) программы не может быть изменен. Компилятор, среда выполнения и процессор должны подчиняться семантике «как если бы — последовательная».
Популярное понимание этого предложения состоит в том, что семантика «как если бы» гарантирует, что результат выполнения однопоточной программы не изменится.
По сути, то же значение, что и у правила «происходит до»: правило «происходит до» гарантирует, что результат выполнения одного потока и правильно синхронизированной многопоточности не будет изменен. Все гарантируют результат выполнения, но не процесс выполнения.
Это тоже изюминка дизайна JMM: он не только обеспечивает удобство и корректность программирования для программистов, но и обеспечивает большую свободу оптимизации для компиляторов и процессоров.
Использованная литература:
"Углубленное понимание модели памяти Java"
"Глубокое понимание виртуальной машины Java"
Искусство параллельного программирования на Java