单例懒汉模式的并发问题

在平时的开发中,单例懒汉模式经常会用到。如果单例没设计好,在高并发的场景下会出现一些问题,当然如果是单线程执行是不会有问题出现的。

举个栗子:

/**
 * @author: htc
 * @date: 2020/8/28 11:26
 * @descr:
 */
public class Cpu {
    private static Cpu instance;
    private Cpu(){}
    public static Cpu getInstance(){
        //第一次检测
        if (instance==null){                                // row 1
            //同步
            synchronized (Cpu.class){                       // row 2
                if (instance == null){                      // row 3
                    //多线程环境下可能会出现问题的地方
                    instance = new  Cpu();                  // row 4
                }
            }
        }
        return instance;
    }
    
    public void getCount() {
        //这里是相关业务代码,省略。。。
    }
}

首先我们说下为什么要用双if多次判空:

首先假如我们先去掉row3行的判空逻辑。现在有两个线程A、B。当A线程拿到锁并成功new出一个instance准备释放但还没释放锁(或者是时间片刚好轮转到其他线程),在此期间B线程已执行完row1的判空逻辑,正在等待锁的释放。这时instance实际上是已经创建好了。这时当A线程再次获得时间片释放掉锁,紧接着锁被B线程后会再去对instance进行实例化一次,就会导致不同线程调用getInstance方法时,得到的不是同一个对象。

如果加上row3行判空逻辑,当B线程拿到锁后对instance再次进行判空,这时就会跳过row4行的代码执行,避免上面的问题。

然而,单单经过如上改造是完全不够的,在单线程环境下依然没问题,在高并发场景下还是会出现问题,

问题的关键点还是出在row4行代码。我们知道在javac命令将java文件编译成class文件,再由javap转成字节码文件的时候,可能出现指令重排。比如row4行代码的字节码(伪代码): (这里补充下:synchronized 只会保证原子性,并不会保证有序性,所以还是有可能发生指令重排)

//1、分配对象内存空间
memory = allocate();

//2、初始化对象
instance(memory);

//3、设置instance指向刚分配的内存地址,此时instance应该不为null
instance = memory;

/*
* 由于步骤2 和 步骤3不存在数据依赖关系可能会重排序,而且无论重排前还是重排后程序的执行结果在单线程中并没有改变,这种重排优化是允许的。
* 但指令重排只会保证串行语义执行的一致性(即单线程中),并不会关系多线程的语义一致性
*
* 所以重排后的伪代码可能如下
*/
//1、分配对象内存空间
memory = allocate();

//3、设置instance指向刚分配的内存地址,此时instance应该不为null
instance = memory;

//2、初始化对象
instance(memory);

当指令重排后的结果如上时,线程A执行完instance=memory后刚好发生时间片轮转,此时B线程进行第一次判空(即执行row1),这是instance是不为Null的,然后B线程就直接向instance返回。实际上这会instance并没有完成实例化,这会对于B线程的调用者拿到的instance去执行其他逻辑代码时(如这里的getCount()方法)就会出问题。

如何避免呢?,其实很简单,归根究底是因为row4行代码在编译过程中发生了指令重排,这里只要禁止编译时这行代码指令重排即可。解决方法为在定义instance时添加volatile关键字即可,

private volatile static Cpu instance;

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值