本文已同步到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爲線程安全。
經過這個例子可見,只要掌握了分析多線程安全的要點,找到緣由,咱們也能夠在解決問題時提出不同的解決方案。