文档章节

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

那只是一股逆流
 那只是一股逆流
发布于 2016/12/17 10:37
字数 989
阅读 388
收藏 15

本文已同步到http://liumian.win/2016/12/17/dcl-without-volatile/

上一篇博客中提到双重检测锁的无volatile实现,如何实现呢?那么在这篇博客中来一探究竟吧~

无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归根结底是对象的不安全发布:对象还没有构造好,就将其发布出去了。

仔细与不安全的(无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),保证了在赋值操作(临时变量赋值给单例变量)之前对象的初始化一定完成了。为什么?

重点来了

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

  1. 在线程内表现为串行的语义中,JVM保证了临时变量调用toString方法(或任意方法)时,该对象一定初始化完成了!请思考体会一下表现为串行的含义。
  2. 而程序次序规则又保证了DCL temp = new DCL()Happens-Beforeinstance = temp,即前面的赋值操作一定对后面这个赋值操作可见

总结

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

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

© 著作权归作者所有

共有 人打赏支持
那只是一股逆流
粉丝 9
博文 22
码字总数 26214
作品 0
南岸
后端工程师
私信 提问
Java设计模式 - 单例模式(末尾有彩蛋😄)

定义   确保只有一个类只有一个实例,并提供全局访问点。 为什么要使用它   对一些类来说保证只有一个实例是很重要的。比如windows操作系统中的资源管理器,回收站等工具必须保证只有一个...

那只是一股逆流
2016/11/24
15
0
如何正确地写出单例模式

单例模式算是设计模式中最容易理解,也是最容易手写代码的模式了吧。但是其中的坑却不少,所以也常作为面试题来考。本文主要对几种单例写法的整理,并分析其优缺点。很多都是一些老生常谈的问...

Henrykin
2016/12/16
7
0
单例模式的N种写法(Java版)

1.懒汉式,线程不安全 这段代码简单明了,而且使用了懒加载模式,但是却存在致命的问题。当有多个线程并行调用 getInstance() 的时候,就会创建多个实例。也就是说在多线程下不能正常工作 2....

晨猫
07/10
0
0
设计模式学习-1-单例模式

单例模式算是设计模式中最容易理解,也是最容易手写代码的模式了吧。但是其中的坑却不少,所以也常作为面试题来考。本文主要对几种单例写法的整理,并分析其优缺点。很多都是一些老生常谈的问...

iSnowFlake
2015/12/11
33
0
最全的单例模式实现及各实现方式比较

单例模式算是设计模式中最容易理解,也是最容易手写代码的模式了吧。但是其中的坑却不少,所以也常作为面试题来考。本文主要对几种单例写法的整理,并分析其优缺点。很多都是一些老生常谈的问...

问题达人
2016/09/01
5
0

没有更多内容

加载失败,请刷新页面

加载更多

Spring源码学习笔记-1-Resource

打算补下基础,学习下Spring源码,参考书籍是《Spring源码深度解析》,使用版本是Spring 3.2.x,本来想试图用脑图记录的,发现代码部分不好贴,还是作罢,这里只大略记录下想法,不写太细了 ...

zypy333
今天
10
0
RestClientUtil和ConfigRestClientUtil区别说明

RestClientUtil directly executes the DSL defined in the code. ConfigRestClientUtil gets the DSL defined in the configuration file by the DSL name and executes it. RestClientUtil......

bboss
今天
17
0

中国龙-扬科
昨天
2
0
Linux系统设置全局的默认网络代理

更改全局配置文件/etc/profile all_proxy="all_proxy=socks://rahowviahva.ml:80/"ftp_proxy="ftp_proxy=http://rahowviahva.ml:80/"http_proxy="http_proxy=http://rahowviahva.ml:80/"......

临江仙卜算子
昨天
11
0
java框架学习日志-6(bean作用域和自动装配)

本章补充bean的作用域和自动装配 bean作用域 之前提到可以用scope来设置单例模式 <bean id="type" class="cn.dota2.tpye.Type" scope="singleton"></bean> 除此之外还有几种用法 singleton:......

白话
昨天
10
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部