volatile解析

volatile是java提供的一种轻量级的同步机制,可以理解为一个变量的同步锁。相比于重量级锁synchronized而言,synchronized大部分时候都是锁的方法或者代码块,而volatile只是锁一个变量,所以volatile更加轻量,当然现在jdk对synchronized的性能越来越优化,也没有想象中的那么重,在平时需要保证一个方法线程安全时,可以放心的使用synchronized。

原理

计算机系统在进行计算时,cpu会从内存中读取数据缓存在自己的寄存器(cpu中空间很小的一块内存空间,方便取值计算,不用每次都读取内存)中,现在大部分计算机都是多核计算机,有多个cpu,这里的寄存器对其他cpu是不可见的,如下图

1577063433170

当把一个变量声明为volatile类型后,这个变量不会被缓存在cpu的寄存器中,每次都是直接操作的内存空间,因此其他线程每次读取的都是最新的值。

特性

  • 变量可见性:volatile变量对所有线程都是可见的,一个线程修改了变量的值,那么其他线程会立即获取这个新值。
  • 禁止指令重排序:在jvm编译器和cpu中,有时候会为了优化效率会对正常的操作指令进行重新排序,volatile变量会禁止指令重排序。

举例

我们熟悉的单例模式中,有一种安全的写法,如下双重检查的写法

public class Single {

    private static volatile Single single = null;

    private Single() {
    }

    public static Single getInstance() {
        if (single == null) {
            synchronized (single) {
                if (single == null) {
                    single = new Single();
                }
            }
        }
        return single;
    }
}复制代码

代码中将Single对象声明成了volatile对象,如果没有volatile会出现什么情况呢?我们看一下single = new Single()这句代码,正常的操作会是这样:

  1. 分配内存地址M;
  2. 在内存M上初始化Single对象;
  3. 将M的地址赋值给single对象;

但编译期会出现指令重排,操作会变成这样:

  1. 分配内存地址M;
  2. 将M的地址赋值给single对象;
  3. 在内存M上初始化Single对象;

回到代码中,当A线程执行single = new Single()中的第二步时,B线程进入getInstance()方法,先判断 if (single == null),这时single对象上已经有内存地址了,B线程会直接返回single对象,但是这个线程拿到的是个空对象,内存M上还没有初始化对象。

将single变量声明成volatile时,就会避免这类指令重排的问题。

结论

当我们程序中出现可能多个线程操作同一个共享变量时,要保证这个共享变量的线程安全,可以将这个变量声明成volatile。

猜你喜欢

转载自juejin.im/post/5e1c1bd8518825261546e316