MST

星途 面试题库

面试题:深入剖析Java内存模型与指令重排优化

详细说明Java内存模型是如何防止指令重排对多线程程序造成错误的,以及happens - before原则在其中起到的作用,并结合具体的Java代码示例进行分析。
46.3万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试

Java内存模型防止指令重排及happens - before原则作用

  1. Java内存模型防止指令重排机制
    • 内存屏障:Java内存模型通过插入特定类型的内存屏障(Memory Barrier)来禁止特定类型的处理器重排序。例如,volatile关键字在实现时会利用内存屏障。写volatile变量时,会在写操作后插入一个StoreStore屏障和一个StoreLoad屏障,以确保写操作对其他线程可见,并防止后续指令重排到写操作之前;读volatile变量时,会在读操作前插入一个LoadLoad屏障和一个LoadStore屏障,确保读操作能获取到最新的值,并防止读操作重排到后面的指令之前。
    • 顺序一致性内存模型:Java内存模型在单线程环境下,程序的执行顺序是按照代码顺序执行的,这与顺序一致性内存模型类似。而在多线程环境下,通过内存屏障等机制,确保在遵守happens - before原则的情况下,程序的执行结果符合顺序一致性内存模型的预期,从而防止指令重排对多线程程序造成错误。
  2. 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();
    }
}
  1. 没有volatile修饰value
    • 如果value没有被volatile修饰,在thread1中,写操作1和写操作2可能会发生指令重排。比如,先执行flag = 1,后执行value = 1。这样当thread2flag变为1后,读取value可能仍然是0,这就导致了错误的结果。
  2. volatile修饰value
    • 因为valuevolatile修饰,写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,防止了指令重排可能带来的错误。