Volatile关键字

8

一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:

  1. 保证线程间的可见性

  2. 禁止进行指令重排序

保证线程间的可见性

public class ForeverLoop {
    // 静态变量,类加载时初始化为false(所有线程共享)
    // 问题:未声明volatile,导致多线程可见性问题
    static boolean flag = false;
​
    public static void main(String[] args) {
        // 创建并启动新线程(修改flag的线程)
        new Thread(() -> {
            try {
                Thread.sleep(100);  // 延迟100ms,确保主线程先进入循环
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            flag = true;  // 修改flag的值
            System.out.println("修改停止标志为true...");  // 预期输出修改后的值
        }).start();
​
        // 主线程执行foo方法
        foo();
    }
​
    static void foo() {
        int i = 0;
        // 无限循环检查flag状态
        // 问题:主线程可能永远看不到新线程对flag的修改
        while (!flag) {
            i++;  // 自增计数器(理论上会持续增长)
        }
        // 当flag变为true时输出结果(实际可能无法到达此处)
        System.out.println("已停止... 计数:" + i);
    }
}

运行结果

原因:

变量未声明为 volatile

在java中,每个线程会从主内存复制变量到工作内存,如果不使用volatile进行操作,线程对变量的感知可能只停留在工作内存中,其他线程无法立即感知到变化。

  • 主线程视角flag 的初始值为 false,主线程的 while 循环会持续读取本地内存中的 false

  • 新线程视角:修改 flagtrue 后,仅更新了新线程的本地内存,未同步到主内存,主线程无法感知。

JIT 编译器的优化逻辑

JIT 编译器在运行时对热点代码(频繁执行的代码)进行优化,目标是提升执行效率。在该案例中,主线程的 while (!flag) 循环是典型的热点代码。

热点代码检测

  • 触发条件:当 while (!flag) 循环被频繁执行(如达到 10,000 次),JIT 会将其标记为“热点代码”。

  • 优化目标:减少循环中的冗余操作(如重复读取 flag)。

循环不变量传播

  • 优化逻辑: JIT 分析代码后发现,flag 在循环体内未被修改,且未受同步机制保护(如 volatile 或锁)。 因此,JIT 认为 flag 的值在循环过程中是恒定的(即使实际可能被其他线程修改)。

  • 最终优化:将循环条件替换为 true,彻底跳过 flag 的检查。

JIT 可能执行以下优化:

上述代码

while (!flag) {
i++;
}

在很短的时间内,这个代码执行的次数太多了,当达到了一个阈值,JIT就会优化此代码,如下:

while (true) {
i++;
}

当把代码优化成这样子以后,即使flag变量改变为了false也依然停止不了循环

解决方案:

1.在程序运行的时候加入vm参数-Xint表示禁用即时编辑器,不推荐,得不偿失(其他程序还要使用)

2.在修饰stop变量的时候加上volatile,表示当前代码禁用了即时编辑器,问题就可以解决,代码如下:

static volatile boolean stop = false;

禁止进行指令重排序

用 volatile 修饰共享变量会在读、写共享变量时加入不同的屏障,阻止其他读写操作越过屏障,从而达到阻止重排序的效果

注解@Actor保证方法内的代码在同一个线程下执行

为什么不能是volatile x 呢?

  • 所以,现在我们就可以总结一个volatile使用的小妙招:

    • 写变量让volatile修饰的变量的在代码最后位置

    • 读变量让volatile修饰的变量的在代码最开始位置