單例模式--雙重檢驗鎖真的執行緒安全嗎
原創-轉載請註明出處。
單例模式是我們最熟悉不過的一種設計模式,用來保證記憶體中只有一個物件的例項。雖然容易,但裡面的坑也有很多,比如雙重檢驗鎖模式(double checked locking pattern)真的是執行緒安全的嗎?
起因
在對專案進行PMD靜態程式碼檢測時,遇到了這樣一個問題
Partially created objects can be returned by the Double Checked Locking pattern when used in Java. An optimizing JRE may assign a reference to the baz variable before it calls the constructor of the object the reference points to.
Note: With Java 5, you can make Double checked locking work, if you declare the variable to be volatile.
大概意思是,使用雙重檢驗鎖模式,可能會返回一個部分初始化的物件。可能大家有些疑慮,什麼是部分初始化的物件,我們下面繼續分析
什麼是雙重檢驗鎖模式
public static Singleton getSingleton() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance ; }
我們看到,在同步程式碼塊的內部和外部都判斷了instance == null,這時因為,可能會有多個執行緒同時進入到同步程式碼塊外的if判斷中,如果在同步程式碼塊內部不進行判空的話,可能會初始化多個例項。
問題所在
這種寫法看似完美無缺,但它卻是有問題的,或者說它並不擔保一定完美無缺。主要原因在於instance = new Singleton();並不是原子性的操作。
建立一個物件可以分為三部:
1.分配物件的記憶體空間
2.初始化物件
3.設定instance指向剛分配的記憶體地址
當instance指向分配地址時,instance不為空
但是,2、3部之間,可能會被重排序,造成建立物件順序變為1-3-2.試想一個場景:
執行緒A第一次建立物件Singleton,物件建立順序為1-3-2;
當給instance分配完記憶體後,這時來了一個執行緒B呼叫了getSingleton()方法
這時候進行instance == null的判斷,發現instance並不為null。
但注意這時候instance並沒有初始化物件,執行緒B則會將這個未初始化完成的物件返回。那B執行緒使用instance時就可能會出現問題,這就是雙重檢查鎖問題所在。
使用volatile
對於上述的問題,我們可以通過把instance宣告為volatile型來解決
public class Singleton{ private volatile static Singleton instance; public static Singleton getSingleton() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance ; } }
但是必須在JDK5版本以上使用。
靜態內部類
public class Singleton { private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } private Singleton (){} public static final Singleton getInstance() { return SingletonHolder.INSTANCE; } }
這種寫法是目前比較推薦的一種寫法,採用靜態內部類的方式,即實現了懶載入又不會出現執行緒安全問題。而且減少了synchronized的開銷。