关于双重检测锁的一种无volatile实现
2016-12-14 23:36
393 查看
本文已同步到http://liumian.win/2016/12/17/dcl-without-volatile/
上一篇博客中提到双重检测锁的无volatile实现,如何实现呢?那么在这篇博客中来一探究竟吧~
仔细与不安全的(无volatile)的DCL相比较,我们在同步代码块里面这三行代码发生了改变:
对应三个步骤:
1. 创建临时变量
2. 调用临时变量的方法
3. 将临时变量的引用赋值给单例变量
为什么要引入临时变量呢?
为什么要调用临时变量的方法呢?
大家在心里肯定会有这些疑问,别急,我来一个一个的回答。
为什么要引入临时变量?
引入临时变量的目的是将对象的初始化(new、invokespecial)与赋值(astore)强行分开。但是仅仅通过一个临时变量中转是不够的,请看下面这个问题的分析。
为什么要调用临时变量的方法?
如果没有调用临时变量的方法这一行代码:
并不能解决解决根本问题:对象的不安全发布。因为JVM依然有可能对这三条指令:new、involvespecial以及两条astore指令(分别对应的是赋值给temp,temp赋值给instance,因为线程内表现为串行的语义的存在,这两条赋值指令的顺序不能改变),所以对于JVM来说那两行代码和这一行代码并没有特殊的地方:
现在问题的关键便落在了
上一个问题的回答中我们提到了解决问题的思路:将对象的初始化(new、invokespecial)与赋值(astore)强行分开。其实这行代码起到的作用相当于一个内存屏障,将两个操作(初始化和赋值)强行分开。
因为
重点来了
因为这段代码在同步代码块中,所以保证了只有一条线程串行执行这几行代码,所以同时满足了
在线程内表现为串行的语义中,JVM保证了临时变量调用toString方法(或任意方法)时,该对象一定初始化完成了!请思考体会一下
而程序次序规则又保证了
通过这个例子可见,只要掌握了分析多线程安全的要点,找到原因,我们也可以在解决问题时提出不一样的解决方案。
上一篇博客中提到双重检测锁的无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 } }
再分析原因
无volatile修饰的DCL归根结底是对象的不安全发布:对象还没有构造好,就将其发布出去了。
仔细与不安全的(无volatile)的DCL相比较,我们在同步代码块里面这三行代码发生了改变:
DCL temp = new DCL(); temp.toString(); instance = temp;
对应三个步骤:
1. 创建临时变量
2. 调用临时变量的方法
3. 将临时变量的引用赋值给单例变量
为什么要引入临时变量呢?
为什么要调用临时变量的方法呢?
大家在心里肯定会有这些疑问,别急,我来一个一个的回答。
为什么要引入临时变量?
引入临时变量的目的是将对象的初始化(new、invokespecial)与赋值(astore)强行分开。但是仅仅通过一个临时变量中转是不够的,请看下面这个问题的分析。
为什么要调用临时变量的方法?
如果没有调用临时变量的方法这一行代码:
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),保证了在赋值操作(临时变量赋值给单例变量)之前对象的初始化一定完成了。为什么?
重点来了
因为这段代码在同步代码块中,所以保证了只有一条线程串行执行这几行代码,所以同时满足了
线程内表现为串行的语义和
程序次序规则,
在线程内表现为串行的语义中,JVM保证了临时变量调用toString方法(或任意方法)时,该对象一定初始化完成了!请思考体会一下
表现为串行的含义。
而程序次序规则又保证了
DCL temp = new DCL()Happens-Before
instance = temp,即前面的赋值操作一定对后面这个赋值操作可见
总结
通过线程内表现为串行的语义和
程序次序规则这两条规则的叠加使用,我们做到了在不使用volatile关键字修饰的情况下DCL为线程安全。
通过这个例子可见,只要掌握了分析多线程安全的要点,找到原因,我们也可以在解决问题时提出不一样的解决方案。
相关文章推荐
- 关于双重检测锁的一种无volatile实现
- 关于网站在线用户在线检测的实现
- 关于Python中一种回调方式的实现
- android关于实现EditText中加多行下划线的的一种方法
- 关于“单例模式”的另外一种实现
- C#下的单例实现本文介绍了C#下关于单例的三种实现方法。 最普通的一种: 以下是引用片段: using System; public class Singleton { pr
- C++/CLI singleton模式 (双重检测锁实现)
- opencv关于图像金字塔Lucas-Kanade光流检测的实现
- 关于异步加载数据的又一种实现
- 关于listview圆角实现的一种方法
- 关于Opencv实现人脸检测的阐述
- 串口波特率自动检测的一种工程实现:分段特征值匹配
- 一种Java与OpenCV结合实现的目标检测模块
- 关于JAVA回调方法的一种普遍实现
- 关于异步加载数据的又一种实现
- Linux检测TCP连接断开的一种简单实现方法
- PHP关于检测用户是否登陆,以及设置拦截器(简单实现)
- PHP关于检测用户是否登陆,以及设置拦截器(简单实现)
- 关于Intent传输Map,List的一种实现方式
- 关于Harris角点检测的实现