不少人熟知單例模式中有一種寫法是使用雙重檢查鎖實現的,可是在網上看到的例子基本上都不正確,有些寫是正確,但沒有很好解析,形成不少人沒有真正理解。其中,典型錯誤寫法是這樣的: java
public class Resource { private static Resource resource ; public static Resource getInstance(){ if(resource == null ){ synchronized (Resource.class) { if(resource == null ){ resource = new Resource() ; } } } return resource ; } private Resource(){} }
它基本思路是,首先在沒有同步的狀況下檢查resource是否等於null,若是條件不成立,直接返回resource 。不然,就使用同步再檢查resource是否等於null,條件成立才正真初始化resource。這中方式既保證只有一個線程初始化resource,又能作到延時加載。彷佛是「魚和熊掌可兼得「。 編程
上面程序真正的問題是沒有同步的狀況下讀取共享變量resource,併發的狀況下對象的狀態值有多是過時無效的。要解決這個問題也很簡單,把resource聲明爲volatile類型。volatile有什麼做用?引用《java併發編程實戰》的解析: 設計模式
當一個域聲明爲volatile類型後,編譯器與運行時會監視這個變量:它是共享的,並且對它的操做不會與其餘的內存操做一塊兒被重排序。volatile變量不會緩存在寄存器或緩存在對其餘處理器隱藏的地方。因此,讀一個volatile類型的變量時,總會返回由某一線程所寫入的最新值。
讀取volatile變量比讀取非volatile變量的性能幾乎沒有差異,不過須要注意的是volatile只能保證內存可見性,並不能保證原子性。 緩存
《effective java 》是不支持這種寫法的,推薦使用」惰性初始化holder「或」枚舉「技巧,如: 併發
public class Resource { private static class ResourceHolder{ private static Resource resource = new Resource() ; } private static Resource resource ; public static Resource getInstance(){ return ResourceHolder.resource ; } private Resource(){} }
參考資料 性能
《effective java 》第二版 spa
《java併發編程實戰》 線程
《研磨設計模式》 設計