volatile关键字

volatile的作用:

  • 保证线程可见性
    MESI缓存一致性协议
  • 禁止指令重排序(CPU)
    DCL(Double Check Lock 双重检查锁)单例;
    底层由内存屏障保证。

1. 保证线程可见性

java中所有线程共享堆内存,除了共享内存外,每个线程都有自己的专属区域,都有自己的工作内存。如果在共享内存中有一个值的话,当某个线程访问这个值时会copy一份到自己的工作空间中,先在自己空间中改变后再更新堆内存中的值。在这过程中其他线程可能读到未更新的值。

在这个线程里发生的改变,不能及时反映到另外一个线程里,这就是线程之间的不可见,对这个变量值加了volatile后就能保证一个线程的改变,另外一个线程马上就能看到。CPU底层是使用MESI高速缓存一致性协议实现的。

volatile只能让被它修饰的变量具有可见性,但不能保证具有原子性。比如 volatile int a = 0;之后有一个操作 a++;这个变量a具有可见性,但是a++ 依然是一个非原子操作,也就是这个操作同样存在线程安全问题。

2. 禁止指令重排序

指令重排:参考该篇CPU乱序执行一节

volatile 底层是使用内存屏障实现的,在写操作和读操作前后都加了屏障。(JVM要求这样实现,是一种规范,底层用Lock指令实现)

JVM层面上:

StoreStoreBarrier
volatile 写操作(Store)
StoreLoadBarrier

LoadLoadBarrier
volatile 读操作(Load)
LoadStoreBarrier

DCL单例

参考懒汉式单例 写法3:Singleton单例模式 代码在下方贴出

在这个写法中是需要加volatile的,因为要防止指令重排。

第一个线程INSTANCE = new Mgr04() 经过编译器编译后的指令分为3步:
(1)给指令申请内存
(2)初始化成员变量
(3)把这块内存的内容赋值给INSTANCE。
指令重排后可能还没初始化就已经执行了(3),这时第二个线程过来判断INSTANCE不为null就会直接return INSTANCE,初始化错误。

加了volatile后防止了指令重排,一定能保证INSTANCE按顺序正常初始化。

ps:不加volatile实际上也很难碰到这种问题,但为了完全正确是应该加上的。

public class Mgr04 {

    private static volatile Mgr04 INSTANCE;

    // 构造方法私有化
    private Mgr04() {};

    // 该方法被调用时才创建实例
    public static Mgr04 getInstance() {
        if ( INSTANCE == null) {
            // 双重检查   如果是单次检查,仍有可能有多个线程进入if判断
            synchronized (Mgr04.class) {
                if ( INSTANCE == null ) {
                    INSTANCE = new Mgr04();
                }
            }
        }
        return INSTANCE;
    }

    // 其它方法
    public void m(){};

}

总结

volatile保证线程的可见性,同时防止指令重排序。
线程可见性在CPU级别是用MESI缓存一致性协议来保证的。
禁止指令重排序CPU级别是禁止不了的,那是CPU内部运行过程能提高效率。
但是在虚拟机级别加volatile后可以禁止指令重排,它内部是加了读屏障和写屏障,这是CPU的原语。

你可能感兴趣的:(volatile关键字)