/*
目標:雙重檢查機制,以及使用volatile修飾(最好,最安全,最推薦)
步驟:
1.構造器私有
2.提供一個靜態變量用於存儲一個單例對象
3.提供一個方法進行雙重檢查機制返回單例對象
4.使用volatile修飾靜態的變量
雙重檢查的優點:線程安全,延遲加載,效率較高!
*/
public class Singleton {
private volatile static Singleton INSTANCE;
private Singleton(){
}
public static Singleton getInstance(){
// 第一次檢查:判斷單例對象的變量是否爲null
if(INSTANCE == null){
synchronized (Singleton.class){
//第二次檢查:判斷單例對象的變量是否爲null
if(INSTANCE == null){
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}
爲什麼要使用volatile修飾呢?
-
禁止指令重排序
創建對象要經過如下幾個步驟
a. 分配內存空間
b. 調用構造器,初始化實例
c. 返回地址給引用但是JVM具有指令重排的特性,執行的順序有可能變成 a-c-b,指令重排在單線程下不會出現問題,但是在多線程下會導致一個線程獲得還沒有初始化的實例。例如:線程T1執行了a,b,此時線程T2調用getInstance()方法發現INSTANCE不爲null,因此返回INSTANCE,但此時INSTANCE還未被初始化。
-
保證可見性
由於可見性問題,線程T1在自己的工作線程內創建了實例,但此時還未同步到主內存中,此時線程T2判斷INSTANCE還是null,那麼線程T2又將在自己的工作線程創建一個實例,這樣就創建了多個實例如果加上了volatile修飾INSTANCE之後,保證了可見性,一旦線程T1返回了實例,線程T2可以立即發現INSTANCE不爲null