面试官问我双重检查锁,回答之后他却让我做面试官

在Java程序中,有时候需要采用延迟初始化来降低初始化类和创建对象的开销。双重检查锁定是常见的延迟初始化技术,但它是一个错误的用法。

比如,下面是非线程安全的延迟初始化对象的代码:

public static Instance getInstance() {
  if (instance == null) {							//1:线程A执行
    instance = new Instance();				//2:线程B执行
  }
  return instance;
}

这种情况下,线程A在执行代码1的时候,线程B执行代码2,那么线程A就会看到instance引用还没有完成初始化。

所以为了解决这个问题就给getInstance方法加上了synchronized锁。

public synchronized static Instance getInstance() {
  if (instance == null) {							
    instance = new Instance();				
  }
  return instance;
}

但是synchronized会导致性能开销,如果方法频繁地被多个线程调用,会导致程序执行性能下降。要是没有频繁调用,那还可以。

所以为了解决synchronized巨大开销的问题,提出了“双重检查锁定(Double-checked Locking)”。

public static Instance getInstance() {
  if (instance == null) {							//1.第一次检查
    synchronized(XXX.class) {					//2.加锁
      if (instance == null) {					//3.第二次检查
        instance = new Instance();		//出问题的根源
      }
    }
  }
  return instance;
}

当时这样就会有一个错误的优化,当线程执行第一行读取到instance不为null时,instance引用的对象有可能还没有完成初始化。

为什么会出现这种问题呢?

还要从instance = new Instance();创建一个对象这行代码看起

这一行代码可以分解成如下3行伪代码:

memory = allocate();     	//1:分配对象的内存空间
ctorInstance(memory)			//2:初始化对象
instance = memory					//3:设置instance指向刚分配的内存地址

本来是1-2-3的顺序执行,担忧有可能会被重排序,成1-3-2,当执行到instance = memory的时候,还没有初始化对象呢。

而且最气人的是这个重排序并没有违反intra-thread semantics。因为根据《The Java Language Specification,Java SE 7 Edition》所有线程在执行Java程序时必须遵守intra-thread semantics,它保证了重排序不会改变单线程内程序执行结果

所以为了解决这个问题,我们就请出了volatile关键字了,我们在学单例模型的时候,就会看到这样一句话:“啊 这个volatile可以防止重排序呢 用它!用它!”

只需要给对象加上volatile就好:private volatile static Instance instance;

但是为什么volatile可以防止重排序呢?

volatile通过插入读屏障和写屏障保证可见性,在volatile禁止重排序上,也是通过内存屏障实现的。
因为内存屏障可以使一些指令按照特定顺序执行。
volatile禁止指令重排序的规则:
 1.当第二个操作是voaltile写时,无论第一个操作是什么,都不能进行重排序
 2.当地一个操作是volatile读时,不管第二个操作是什么,都不能进行重排序
 3.当第一个操作是volatile写时,第二个操作是volatile读时,不能进行重排序

除了加上volatile关键字,还有一种解决方法:基于类初始化

JVM在类的初始化阶段(即在Class被加载后,且被线程使用之前),会执行类的初始化。在执行的初始化期间,JVM会去获取一个锁。这个锁可以同步多个线程对同一个类的初始化。

基于这个特性,可以实现另一种线程安全的延迟初始化方案Initialization On Demand Holder idiom

下面这张图是两个线程并发执行getInstance方法的执行图:

在这里插入图片描述

这个方法的思想是:允许出现重排序,但是不允许非构造线程(这里指线程B)“看到”这个重排序。

之前出问题的根源是因为初始化过程的重排序被其他线程看到了,所以才会出错。这种类初始化阶段解决方法就是不让其他线程看到重排序。而volatile方法解决核心是不允许出现重排序

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值