本文已同步到http://liumian.win/2016/12/17/dcl-without-volatile/java
上一篇博客中提到双重检测锁的无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归根结底是对象的不安全发布
:对象尚未构造好,就将其发布出去了。app
仔细与不安全的(无volatile)的DCL相比较,咱们在同步代码块里面这三行代码发生了改变:.net
DCL temp = new DCL(); temp.toString(); instance = temp;
对应三个步骤:线程
为何要引入临时变量呢? 为何要调用临时变量的方法呢? 你们在内心确定会有这些疑问,别急,我来一个一个的回答。code
为何要引入临时变量? 引入临时变量的目的是将对象的初始化(new、invokespecial)与赋值(astore)强行分开。可是仅仅经过一个临时变量中转是不够的,请看下面这个问题的分析。对象
为何要调用临时变量的方法? 若是没有调用临时变量的方法这一行代码:blog
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),保证了在赋值操做(临时变量赋值给单例变量)以前对象的初始化必定完成了。为何?
重点来了
由于这段代码在同步代码块中,因此保证了只有一条线程串行执行这几行代码,因此同时知足了线程内表现为串行的语义
和程序次序规则
,
表现为串行
的含义。DCL temp = new DCL()
Happens-Beforeinstance = temp
,即前面的赋值操做必定对后面这个赋值操做可见经过线程内表现为串行的语义
和程序次序规则
这两条规则的叠加使用,咱们作到了在不使用volatile关键字修饰的状况下DCL为线程安全。
经过这个例子可见,只要掌握了分析多线程安全的要点,找到缘由,咱们也能够在解决问题时提出不同的解决方案。