volatile关键字的作用

保持内存可见性(Memory Visibility):所有线程都能看到共享内存的最新状态。

  • Java通过几种原子操作完成工作内存和主内存的交互:

      lock:作用于主内存,把变量标识为线程独占状态。
      unlock:作用于主内存,解除独占状态。
      read:作用主内存,把一个变量的值从主内存传输到线程的工作内存。
      load:作用于工作内存,把read操作传过来的变量值放入工作内存的变量副本中。
      use:作用工作内存,把工作内存当中的一个变量值传给执行引擎。
      assign:作用工作内存,把一个从执行引擎接收到的值赋值给工作内存的变量。
      store:作用于工作内存的变量,把工作内存的一个变量的值传送到主内存中。
      write:作用于主内存的变量,把store操作传来的变量的值放入主内存的变量中。
    

Java 提供了一种稍弱的同步机制,即volatile 变
量,用来确保将变量的更新操作通知到其他线程。 可以将volatile 看做一个轻量级的锁,但是又与锁有些不同:

  • 对于多线程,不是一种互斥关系
  • 不能保证变量状态的“原子性操作”

volatile如何保持内存可见性
volatile的特殊规则就是:

  • read、load、use动作必须连续出现。
  • assign、store、write动作必须连续出现。

所以,使用volatile变量能够保证:

  • 每次读取前必须先从主内存刷新最新的值。
  • 每次写入后必须立即同步回主内存当中。
  • 也就是说,volatile关键字修饰的变量看到的随时是自己的最新值。线程1中对变量v的最新修改,对线程2是可见的。

防止指令重排

首先我们知道在单例模式的懒汉式中,是存在线程安全问题的,我们会使用synchronized关键字将getInstance方法改为同步方法,并且使用DCL(Double Check Lock,双重检查锁)机制使线程安全。
但其实还存在一个问题——当instance不为null时,仍可能指向一个"被部分初始化的对象"。
class Singleton {
    private static volatile Singleton instance = null;
    private Singleton(){}
    public static Singleton getInstance() {
        if ( instance == null ) { //当instance不为null时,仍可能指向一个“被部分初始化的对象”
            synchronized (Singleton.class) {
                if ( instance == null ) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}
  • 问题其实在这一行:

       instance = new Singleton();
    
  • 它并不是一个原子操作。事实上,它可以”抽象“为下面几条JVM指令:

		memory = allocate();    //1:分配对象的内存空间
		initInstance(memory);   //2:初始化对象
		instance = memory;      //3:设置instance指向刚分配的内存地址
  • 上面操作2依赖于操作1,但是操作3并不依赖于操作2,所以JVM可以以“优化”为目的对它们进行重排序,经过重排序后如下:
		memory = allocate();    //1:分配对象的内存空间
		instance = memory;      //3:设置instance指向刚分配的内存地址(此时对象还未初始化)
		ctorInstance(memory);   //2:初始化对象

可以看到指令重排之后,操作 3 排在了操作 2 之前,即引用instance指向内存memory时,这段崭新的内存还没有初始化——即,引用instance指向了一个"被部分初始化的对象"。此时,如果另一个线程调用getInstance方法,由于instance已经指向了一块内存空间,从而if条件判为false,方法返回instance引用,用户得到了没有完成初始化的“半个”单例。

  • 解决这个该问题,只需要将instance声明为volatile变量:

      private static volatile Singleton instance;
    

也就是说,在只有DCL没有volatile的懒加载单例模式中,仍然存在着并发陷阱。我确实不会拿到两个不同的单例了,但我会拿到“半个”单例(未完成初始化)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值