[Java并发] [6] 单例模式下双重锁定(DCL)的问题和解决方案

来自阅读Java并发编程的艺术读书笔记,按照自己思路写了一些整理,综合了网上的一些博文和资料
本篇来源于Java并发编程的艺术 Ch3.8 内容

同样是一段总结:

  • DCL-Double Check Lock进行初始化动作,可能因重排序(构造-赋值到实例引用)造成线程不安全问题
  • 通过Volatile-instance禁止重排序(Volatile用Barrier来禁止写操作与之前操作的重排序)
    有些麻烦,但是可以同时初始化对象的字段
  • 通过内部类初始化的方法(内部类初始化可以重排序,但是对其他线程不可见)

双重检查锁定 DCL 与延迟初始化

众所周知,单例模式中经常使用DCL双重检查锁定来降低同步的开销:

image-20210127200115918

但是这种做法不一定安全。示例代码的第七行可以分解为如下三行:

// instance = new Instance()
memory = allocate();		// 1. 分配对象内存空间
ctorInstance(memory);		// 2. 初始化对象
instance = memory;			// 3. 设置instance到刚才分配的地址

在代码2/3之间,可能被重排序。
对于单线程程序来讲,虽然2/3重排序了,但只要保证2在4的前面执行,单线程内的结果就不会被改变:

image-20210127200606051

但是在多线程场景下,可能出现问题:线程B在3-2之间插入,发现instance虽然为非空,但是实际上对象还未被正确初始化

image-20210127200649063

解决方案1:基于Volatile

由于Volatile内存模型中确定,3作为对Volatile的写操作,而写操作之前的操作不能与写操作进行重排序,于是2/3之间的重排序被禁止,问题解决

	private volatile static Instance instance;	// 增加volatile关键字

解决方案2:基于类初始化的懒加载

image-20210127201047183

根据类初始化的过程,对于每一个类或接口C,都必须有唯一的初始化锁LC与之对应。从C到LC的映射,由JVM具体自由实现。JVM类初始化期间会获取这个初始化锁,并且每个线程都至少获取一次初始化所以保证初始化已完成。

通过比较Volatile-instance方案和类初始化懒加载方案,显然类初始化更方便;但是Volatile-instance配上DCL能够自由定制(对实例的字段进行初始化)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值