Java volatile关键字深入JVM理解

volatile关键字修饰的变量可以保证可见性

变量用volatile修饰得时候,当一个线程修改了变量,其他线程可以立即读到修改后得值,适用于一写多读得多线程场景。
多线程对变量有复杂操作禁止使用,容易引起线程间并发的一些问题
当一个线程修改了volatile修饰的变量时,cpu运算后会立即写到共享主存中,其他cpu读取的时候会先将自己独有内存的变量副本置为不可用,直接从共享主存中读取数据,可以理解为对于单一的读写操作是只对共享主存操作的
总结:对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入

volatile关键字修饰的变量保持原子性

以上图片两边代码可以理解为等同

当只对变量进行set操作的时候可以保证变量的原子性
以下是当两个线程并发+1操作的情况,线程1和线程2同时对volatile修饰的变量+1,会先将变量读取到cpu运行空间计算,运算完写入到主存,由于并发读取到cpu中的变量都为1,计算完都为2,所以结果为2,但是我们期望为3,就会造成业务异常

在这里插入图片描述
总结:对任意单个volatile变量的读/写具有原子性,但类似于volatile++这种复合操作不具有原子性。

volatile修饰防止指令重排序

对一个volatile域的写,happens-before于任意后续对这个volatile域的读。对于volatile关键字修饰的变量而言,后续的任意对这个变量的读操作必须在这个写操作后边
当程序执行到 volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行;

例如:双重校验单例模式,对象必须使用volatile关键字修饰

uniqueInstance = new Singleton(); 这段代码其实是分为三步执行:

  1. 为 uniqueInstance 分配内存空间
  2. 初始化 uniqueInstance
  3. 将 uniqueInstance 指向分配的内存地址

但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1>3>2。指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程 T1 执行了 1 和 3,此时 T2 调用 getUniqueInstance() 后发现 uniqueInstance 不为空,因此返回 uniqueInstance,但此时 uniqueInstance 还未被初始化。
使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。

public class Singleton {

    private volatile static Singleton uniqueInstance;

    private Singleton() {
    }

    public static Singleton getUniqueInstance() {
        if (uniqueInstance == null) {
            synchronized (Singleton.class) {
                if (uniqueInstance == null) {
                    uniqueInstance = new Singleton();
                }
            }
        }
        return uniqueInstance;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值