DCL失效问题的探讨

作者: 田真的架构人生 | 来源:发表于2017-08-16 21:45 被阅读168次

引子:该问题由单例模式引申而来,涉及到的关键词有:线程安全、同步性能、编译执行、指令重排等。

先看一个简单的单例模式实现

public class InstanceHolder{
private static Instance ins = null;
private Instance(){}

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

线程安全实现

public class InstanceHolder{
private static Instance ins = null;
private Instance(){}

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

还能优化吗?
N天以后,勤奋好学的你看了一本refactoring的书,深深为之着迷,准备重构自己写的代码,现在的你已经不是当初的菜鸟,深知synchronized方法比未同步方法慢100倍!同时你也发现,只有第一次调用该方法时才需要同步,一旦ins创建成功,同步完全没必要!

终极版

public class InstanceHolder{
private static Instance ins = null;
private Instance(){}

public static Instance getInstance(){
if(ins == null){
    synchronized(InstanceHolder.class){
    //第二个线程进来时,有可能第一个线程已经创建了ins,所以再判断一次   
    if(ins == null){
             ins = new Instance();
        }
    //第一个线程有可能在锁释放之前,刷新了主内存数据,导致第二个线程获取到的ins不为null
    }
}
return ins;
}

只是看起来很完美
这种看起来很完美的优化技巧就是double-checked locking,但遗憾地告诉你,根据JLS规范,上面的代码不可靠!线程有可能得到一个不为null,但是构造不完全的对象。
Why?
造成不可靠的原因是编译器为了提高执行效率的指令重排。只要认为在单线程下是没问题的,它就可以进行乱序写入!以保证不要让cpu指令流水线中断。

指令重排
为了提高代码的执行效率,JVM会将执行频率高的代码编译成机器码;而对于频率不高的代码则仍然采用解释执行。
常见的编译优化方式有:
方法内联:免去参数、返回值传递过程
去虚拟化:接口的方法只有一个实现类,进行方法内联
冗余消除:运行时去掉无用代码
还有一些编译优化根据“逃逸分析”技术
标量替换:User u=new User(“zhang3”,18)
String n=“zhang3” int age=18,节省内存
栈上分配:逃逸对象直接在栈上分配,快速,GC及时
同步削除:去掉不必要的同步

new Instance()到底发生了什么?

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

上面的伪代码中2、3步可能重排


DCL失效问题的探讨
DCL失效问题的探讨

解决方案1
Java5以后的版本,可以利用volatile关键字。
Why?
在java5以前,volatile原语不怎么强大,只能保证对象的可见性
但在java5之后,volatile语义加强了,被volatile修饰的对象,将禁止该对象上的读写指令重排序
这样,就保证了线程B读对象时,已经初始化完全了

解决方案2
这也是官方比较推荐的一种方案(effective java 2nd)
点击(此处)折叠或打开
public class InstanceHolder{

private Instance(){}

//Lazy initialization holder class idiom for static fields
private static class Inner{
private static final Instance ins = new Instance()
}

public static Instance getInstance(){
return Inner.ins;
}

原理:一个类只有在被使用时才会初始化,而类初始化过程是非并行的,这些都由JLS能保证。

相关文章

  • DCL失效问题的探讨

    引子:该问题由单例模式引申而来,涉及到的关键词有:线程安全、同步性能、编译执行、指令重排等。 先看一个简单的单例模...

  • 单例模式

    Double Check Lock(DCL) (不推荐) DCL失效问题sSingleton = new DCLS...

  • 并发 - volatile(二)

    问题是最好的导师, 细心是最棒的品质 DCL一定要加volatile吗? 一、DCL DCL全称double ch...

  • [Java多线程编程之十三] DCL缺陷与优化

    一、DCL问题分析   DCL,即Double Check Lock,双重检查锁定,通常使用在懒加载的单例模式中,...

  • DCL失效原因与解决方法

    DCL单例模式 针对延迟加载法的同步实现所产生的性能低的问题,我们可以采用DCL,即双重检查加锁(Double C...

  • 关于今天的讨论

    很可怕,在专业领域里探讨问题时,真的很难尊重领导。 今天讨论质检失效的时候,我真的是一万头草泥马踏过,...

  • MySQL基础——DCL语句

    上篇文章学习了MySQL基础——DQL语句,这篇文章学习MySQL基础——DCL语句。 DCL语句 DCL英文全称...

  • 数据库基本操作3.0

    今日内容 多表查询 \\ 事务DCL 多表查询: 事务 DCL:

  • 4.MySQL多表&事务

    主要内容 1 . 多表查询2 . 事务3 . DCL 多表查询: 事务 DCL:

  • Happens-Before规则与DCL失效原因分析

    先行发生是Java内存模型中定义的两项操作之间的偏序关系,如果操作A先行发生于操作B,其实就是说在发生操作B之前,...

网友评论

    本文标题:DCL失效问题的探讨

    本文链接:https://www.haomeiwen.com/subject/fvywrxtx.html