关于双重检测锁的一种无volatile实现

点击上方 "程序员小乐"关注, 星标或置顶一起成长

每天凌晨00点00分, 第一时间与你相约

每日英文

Sometimes, if you persist in doing that which you least like to do, in the end you will receive that which you want the most.

有时候,坚持了你最不想干的事情之后,便可得到你最想要的东西。

每日掏心

感觉累的时候,也许你正处于人生的上坡路。坚持走下去,你就会发现到达了人生的另一个高度。

来自:那只是一股逆流 | 责编:乐乐

链接:my.oschina.net/liuxiaomian/blog/807486

程序员小乐(ID:study_tech)第 939 次推文  图源:百度

往日回顾:高中生自学代码写LOL外挂1年狂赚500万!落网前刚买下120万保时捷!

     

   正文   


无volatile的安全实现

先上代码

/**
 * Created by liumian on 2016/12/13.
 */
public class DCL {

    private static DCL instance;
    
    private DCL(){}

    private DCL getInstance(){
        if (instance == null){              //1
            synchronized (DCL.class){       //2
                if (instance == null){      //3
                   DCL temp = new DCL();    //4
                   temp.toString();         //5
                   instance = temp;         //6
                }
            }
        }
        return instance;                    //7
    }
}

<!-- more -->

再分析原因

无volatile修饰的DCL归根结底是对象的不安全发布:对象还没有构造好,就将其发布出去了。

关注公众号程序员小乐回复关键字“offer”获取算法面试题和答案。

仔细与不安全的(无volatile)的DCL相比较,我们在同步代码块里面这三行代码发生了改变:

DCL temp = new DCL();    
temp.toString();         
instance = temp;         

对应三个步骤:

  1. 创建临时变量

  2. 调用临时变量的方法

  3. 将临时变量的引用赋值给单例变量

为什么要引入临时变量呢? 为什么要调用临时变量的方法呢? 大家在心里肯定会有这些疑问,别急,我来一个一个的回答。

  1. 为什么要引入临时变量? 引入临时变量的目的是将对象的初始化(new、invokespecial)与赋值(astore)强行分开。但是仅仅通过一个临时变量中转是不够的,请看下面这个问题的分析。

  2. 为什么要调用临时变量的方法? 如果没有调用临时变量的方法这一行代码:

    DCL temp = new DCL();   
    instance = temp;         
    

    并不能解决解决根本问题:对象的不安全发布。因为JVM依然有可能对这三条指令:new、involvespecial以及两条astore指令(分别对应的是赋值给temp,temp赋值给instance,因为线程内表现为串行的语义的存在,这两条赋值指令的顺序不能改变),所以对于JVM来说那两行代码和这一行代码并没有特殊的地方:

    instance = new DCL();      
    

现在问题的关键便落在了temp.toString(); 上一个问题的回答中我们提到了解决问题的思路:将对象的初始化(new、invokespecial)与赋值(astore)强行分开。其实这行代码起到的作用相当于一个内存屏障,将两个操作(初始化和赋值)强行分开。 因为线程内表现为串行的语义的存在,以及Happens-Before的第一条规则:程序次序规则(什么是线程内表现为串行的语义和程序次序规则请参看上一篇博客:从单例模式到Happens-Before),保证了在赋值操作(临时变量赋值给单例变量)之前对象的初始化一定完成了。为什么?

重点来了

因为这段代码在同步代码块中,所以保证了只有一条线程串行执行这几行代码,所以同时满足了线程内表现为串行的语义程序次序规则

关注公众号程序员小乐回复关键字“Java”获取Java面试题和答案。

  1. 在线程内表现为串行的语义中,JVM保证了临时变量调用toString方法(或任意方法)时,该对象一定初始化完成了!请思考体会一下表现为串行的含义。

  2. 而程序次序规则又保证了DCL temp = new DCL()Happens-Beforeinstance = temp,即前面的赋值操作一定对后面这个赋值操作可见

总结

通过线程内表现为串行的语义程序次序规则这两条规则的叠加使用,我们做到了在不使用volatile关键字修饰的情况下DCL为线程安全。

通过这个例子可见,只要掌握了分析多线程安全的要点,找到原因,我们也可以在解决问题时提出不一样的解决方案。

欢迎在留言区留下你的观点,一起讨论提高。如果今天的文章让你有新的启发,欢迎转发分享给更多人。欢迎加入程序员小乐技术交流群,在后台回复“加群”或者“学习”即可。

猜你还想看

阿里、腾讯、百度、华为、京东最新面试题汇集

学习 MySQL 高性能优化原理,这一篇就够了!

聊一聊 Spring 中的线程安全性

最新!薪酬最高的大学专业公布!

关注订阅号「程序员小乐」,收看更多精彩内容

嘿,你在看吗

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值