volatile : 用在:线程共享且可能被修改的变量

1.保证变量的可见性 : 当一个变量被 volatile 修饰时,线程对该变量的修改会 立即写回主内存,同时其他线程的工作内存中该变量的缓存会 失效,必须重新从主内存读取最新值。

2.禁止指令重排序 编译器或 CPU 为了优化性能,可能会对指令进行重排序(不影响单线程结果,但可能破坏多线程正确性)。举例:单例模式的双重检查锁中,volatile 修饰实例变量,可避免 “指令重排序导致的未初始化实例被引用” 问题。

代码:

public class VolatileVisibilityTest {
    // 测试时注释/打开 volatile 对比效果
    static boolean flag = false;

    public static void main(String[] args) {
        // 线程1:循环等待 flag 变为 true
        new Thread(() -> {
            long i = 0;
            System.out.println("线程1开始,flag初始值:" + flag);
            // 循环内增加大量计算,迫使JVM优化(缓存flag)
            while (!flag) {
                i++;
            }
            System.out.println("线程1结束,flag已变为:" + flag + ",循环次数:" + i);
        }, "线程1").start();

        // 线程2:1秒后修改 flag 为 true
        new Thread(() -> {
            try {
                Thread.sleep(1000); // 确保线程1先启动并进入循环
                flag = true;
                System.out.println("线程2已将flag修改为:" + flag);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "线程2").start();
    }
}

这里的代码中

while (!flag) { i++; } 循环计算,jvm中 jit即时编译会将其优化成 while(true) 使得经管 线程修改了状态,线程一还是1一意孤行,它不知道最新的状态

通过: volatile static boolean flag = false; 线程 2 修改 flag 后,线程 1 会立即感知到 flag 变为 true,退出循环并打印结束信息,体现 “可见性”。

其实如果:代码中加了打印语句也会可见的

public class VolatileVisibilityTest {
    // 测试时注释/打开 volatile 对比效果
    static boolean flag = false;

    public static void main(String[] args) {
        // 线程1:循环等待 flag 变为 true
        new Thread(() -> {
            long i = 0;
            System.out.println("线程1开始,flag初始值:" + flag);
            // 循环内增加大量计算,迫使JVM优化(缓存flag)
            while (!flag) {
                i++;
                // 增加无意义计算,强化JVM对flag的缓存
                if (i % 100000000 == 0) {
                    // 偶尔打印,避免JVM认为循环无意义而优化掉
                    System.out.println("线程1仍在等待...");
                }
            }
            System.out.println("线程1结束,flag已变为:" + flag + ",循环次数:" + i);
        }, "线程1").start();

        // 线程2:1秒后修改 flag 为 true
        new Thread(() -> {
            try {
                Thread.sleep(1000); // 确保线程1先启动并进入循环
                flag = true;
                System.out.println("线程2已将flag修改为:" + flag);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "线程2").start();
    }
}

System.out.println() 会间接引发线程上下文切换,进而导致线程重新从主内存读取变量值

System.out 是全局单例 println() 的锁对象是 this(即 System.out 本身)核心是:

  1. 其内部的 synchronized 锁会引发线程阻塞 / 唤醒(上下文切换);

  2. 更重要的是,synchronized 强制线程从主内存读取变量,相当于 “绕过了工作内存的缓存”,让无 volatile 的变量也能偶尔被刷新。