版權聲明:本文由吳仙傑創做整理,轉載請註明出處:http://www.javashuo.com/article/p-fshjysmn-eh.htmljava
在開始分析雙重加鎖單例代碼以前,咱們須要先理解 java 內存模式的重排序和無序寫入特性。segmentfault
在計算機中,軟件技術和硬件技術有一個共同的目標:在不改變程序執行結果的前提下,儘量的開發並行度。安全
一樣 Java 爲了實現這一目標,在它的編譯和處理時會對代碼進行從新排序,從而達到更高的並行度提高程序性能。多線程
Java 在進行重排序操做時會遵照數據依賴性,即編譯器和處理器不會改變存在數據依賴關係的兩個操做的執行順序。函數
關於對於重排序的講解,強烈推薦閱讀程曉明寫的《深刻理解Java內存模型(二)——重排序》。性能
as-if-serial 語義
: 單線程下,爲了優化能夠對操做進行重排序。優化
Java 編譯器和處理器爲單個線程實現了 as-if-serial 語義,但對於多線程並不實現 as-if-serial 語義。線程
若程序定義的變量之間沒有依賴關係,那麼這兩個變量在 JVM 中的加載順序是不肯定的。code
單例模式帶來的好處:對象
方便共享通用的資源。
避免頻繁操做共享資源所帶來的性能消耗。
而咱們已單例模式有有餓漢式(static
變量,在類加載時就進行初始化一次)與懶漢式(在使用到時才初始化一次)兩種,考慮到對於始初化單例類的開銷較大,每每咱們須要建立單例是懶加載的,即在程序使用到單例時才建立,從而能夠避免建立單例時拖慢程序的啓動速度。
因此對於使用單例模式有兩個要求:(1)懶加載。(2)多線程安全。
DCL(double-checked locking) 即雙重檢查加鎖。由於 DCL 模式的單例是懶加載的,因此這每每也是在許多項目中最容易見到的單例模式寫法。可是這種方式建立的單例,是多線程安全的嗎?
對於雙重檢查加載的單例代碼:
package com.wuxianjiezh.demo.threadpool; public class Singleton { private static Singleton instance; // 私有化的構造方法,保證外部的類不能經過構造器來實例化 private Singleton() { } // 雙重檢查加鎖來獲取對象單例 public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } }
假設有二個線程要獲取上面的單例,當其中 線程一
進入同步塊執行到 instance = new Singleton();
時,線程二
來到了 鎖
外的第一個 null
判斷。注意這裏,線程一
在執行 instance = new Singleton();
這段代碼時有如下幾個步驟,其中執行是無序的(無序寫入),可能出現下面這種狀況:
// 1. 爲 Singleton 對象分配內存 memory = allocate(); // 2. 注意如今 instance 是非空的,但還沒初始化 instance = memory; // 3. 調用 Singleton 的構造函數,傳遞 instance ctorSingleton(instance);
當在執行到 instance = memory;
時,線程二
進入了第一次的 null
判斷,此才 線程二
判斷 instance
不爲 null
,返回了 instance
,但此時返回的不是單例的實例對象,而是內存對象。
使用靜態內部類:
package com.wuxianjiezh.demo.threadpool; public class Singleton { // 私有化的構造方法,保證外部的類不能經過構造器來實例化 private Singleton() { } // 靜態內部類只會被加載一次 // 內部類 SingletonHolder 只有在 getInstance() 方法第一次調用的時候纔會被加載(實現了lazy) // 並且其加載過程是線程安全的(多線程安全) private static class SingletonHolder { // 單例變量 // 常規寫法 // private static final Singleton instance = new Singleton(); // 假設單例對象構造方法會拋出異常時的寫法 private static final Singleton instance; static { instance = new Singleton(); } } // 獲取單例對象實例 public static Singleton getInstance() { return SingletonHolder.instance; } }