volatile

特性:

  1. 可见性:多线程下,一个线程对变量的修改,能立即被其他线程看到.
  2. 禁止指令重排序:编译器和CPU在变量前后的指令不能被乱序执行,本质是JMM会加入内存屏障保证,顺序不会乱序。

volatile在Java里面的运算操作符并非原子操作,这导致volatile变量的运算在并发下一样是不安全的.
案例:下面的案例中,虽然race是volatile修饰的,但是volatile并不是原子操作。

public class VolatileTest {
    private static volatile int race = 0;

    public void increase() {
        race++;
    }

    public int getRace() {
        return race;
    }

    public static void main(String[] args) throws InterruptedException {
        VolatileTest volatileTest = new VolatileTest();
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                volatileTest.increase();
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                volatileTest.increase();
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(volatileTest.getRace());
    }
}

对于指令重排序的案例:

public class Singleton {
    private static Singleton instance;
    public Singleton getInstance(){
               if(Objects.isNull(instance)){   // 多线程可能同时进入
                    instance = new Singleton();
               }
        return instance;
    }

    public static void main(String[] args) {
        Singleton singleton = new Singleton();
        new Thread(()->{
            Singleton instance = singleton.getInstance();
            System.out.println(instance.toString());
        }).start();

        new Thread(()->{
            Singleton instance = singleton.getInstance();
            System.out.println(instance.toString());
        }).start();

        new Thread(()->{
            Singleton instance = singleton.getInstance();
            System.out.println(instance.toString());
        }).start();
    }
}

instance = new Singleton();

  1. 分配内存给对象
  2. 初始化对象(执行构造函数)
  3. 把对象地址赋值给 instance
    在没有 volatile 的情况下,CPU 可能发生指令重排序
  4. 分配内存
  5. 把地址赋给 instance(此时 instance != null)
  6. 初始化对象
    问题:如果线程 A 执行到第 2 步(还没初始化完),线程 B 看到 instance != null 就拿去用了,就出错了!
    下图中,左边红色框中这行,用字节码翻译过来时右边红框这4行,而蓝色框这两行就是初始化过程中会发生指令重排序,当线程A执行INVOKESPECIAL时,线程B来判断instance!=null,会获取一个半初始化的instance实例。所以Singleton必须加volatile。
    "半初始化对象"指的是对象已经被分配内存,但是还没有完成构造函数的初始化。这种情况下,其他线程读取到的对象虽然不为 null,但其内部状态是未定义的,可能导致程序行为异常甚至崩溃。
    image
    正确的做法:
public class Singleton {
    private static volatile Singleton instance;

    private Singleton() {} // 私有构造器

    public static Singleton getInstance() {
        if (instance == null) {               // 外层非同步,提高性能
            synchronized (Singleton.class) {
                if (instance == null) {       // 内层同步,线程安全
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}
posted @ 2025-07-04 14:23  Charlie-Pang  阅读(7)  评论(0)    收藏  举报