Java内存模型防止指令重排及happens - before原则作用
- Java内存模型防止指令重排机制
- 内存屏障:Java内存模型通过插入特定类型的内存屏障(Memory Barrier)来禁止特定类型的处理器重排序。例如,
volatile
关键字在实现时会利用内存屏障。写volatile
变量时,会在写操作后插入一个StoreStore屏障和一个StoreLoad屏障,以确保写操作对其他线程可见,并防止后续指令重排到写操作之前;读volatile
变量时,会在读操作前插入一个LoadLoad屏障和一个LoadStore屏障,确保读操作能获取到最新的值,并防止读操作重排到后面的指令之前。
- 顺序一致性内存模型:Java内存模型在单线程环境下,程序的执行顺序是按照代码顺序执行的,这与顺序一致性内存模型类似。而在多线程环境下,通过内存屏障等机制,确保在遵守happens - before原则的情况下,程序的执行结果符合顺序一致性内存模型的预期,从而防止指令重排对多线程程序造成错误。
- happens - before原则作用
- 定义可见性和有序性:happens - before原则定义了两个操作之间的偏序关系,它为程序员提供了一种手段来推理多线程程序中操作的可见性和顺序性。如果一个操作A happens - before另一个操作B,那么A的执行结果对B可见,并且A的操作按顺序先于B执行。
- 指导编译器和处理器优化:编译器和处理器在优化代码时,必须遵守happens - before原则。这就保证了在多线程环境下,即使实际执行时指令可能重排,但只要遵守happens - before原则,程序的执行结果就是正确的。
代码示例分析
public class VolatileExample {
private static volatile int value = 0;
private static int flag = 0;
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(() -> {
value = 1; // 写操作1
flag = 1; // 写操作2
});
Thread thread2 = new Thread(() -> {
while (flag == 0) {
// 等待flag变为1
}
System.out.println("value: " + value); // 读操作
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
}
}
- 没有
volatile
修饰value
时:
- 如果
value
没有被volatile
修饰,在thread1
中,写操作1和写操作2可能会发生指令重排。比如,先执行flag = 1
,后执行value = 1
。这样当thread2
中flag
变为1后,读取value
可能仍然是0,这就导致了错误的结果。
volatile
修饰value
时:
- 因为
value
被volatile
修饰,写value
操作(写操作1)和读value
操作之间满足happens - before关系。thread1
中的写操作1 happens - before thread2
中的读操作。同时,由于flag
变量的存在,thread1
中的写操作2 happens - before thread2
中的读flag
操作,进而通过传递性,thread1
中的写操作1也happens - before thread2
中的读操作。这样就保证了thread2
读取到的value
一定是最新的值1,防止了指令重排可能带来的错误。