/**
* 單例模式-雙重校驗鎖
* @author szekinwin
*
*/
public class SingleTon3 {安全
private SingleTon3(){}; //私有化構造方法
private static volatile SingleTon3 singleTon=null;
public static SingleTon3 getInstance(){性能
//第一次校驗
if(singleTon==null){ 優化
synchronized(SingleTon3.class){線程
//第二次校驗對象
if(singleTon==null){
singleTon=new SingleTon3();
}
}
}
return singleTon;
}
內存
public static void main(String[]args){
for(int i=0;i<200;i++){
new Thread(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName()+":"+SingleTon3.getInstance().hashCode());
}
}).start();
}get
}
}同步
注意事項:
問題:爲何須要兩次判斷if(singleTon==null)?hash
分析:第一次校驗:因爲單例模式只須要建立一次實例,若是後面再次調用getInstance方法時,則直接返回以前建立的實例,所以大部分時間不須要執行同步方法裏面的代碼,大大提升了性能。若是不加第一次校驗的話,那跟上面的懶漢模式沒什麼區別,每次都要去競爭鎖。class
第二次校驗:若是沒有第二次校驗,假設線程t1執行了第一次校驗後,判斷爲null,這時t2也獲取了CPU執行權,也執行了第一次校驗,判斷也爲null。接下來t2得到鎖,建立實例。這時t1又得到CPU執行權,因爲以前已經進行了第一次校驗,結果爲null(不會再次判斷),得到鎖後,直接建立實例。結果就會致使建立多個實例。因此須要在同步代碼裏面進行第二次校驗,若是實例爲空,則進行建立。
須要注意的是,private static volatile SingleTon3 singleTon=null;須要加volatile關鍵字,不然會出現錯誤。問題的緣由在於JVM指令重排優化的存在。在某個線程建立單例對象時,在構造方法被調用以前,就爲該對象分配了內存空間並將對象的字段設置爲默認值。此時就能夠將分配的內存地址賦值給instance字段了,然而該對象可能尚未初始化。若緊接着另一個線程來調用getInstance,取到的就是狀態不正確的對象,程序就會出錯。
(4)靜態內部類:一樣也是利用了類的加載機制,它與餓漢模式不一樣的是,它是在內部類裏面去建立對象實例。這樣的話,只要應用中不使用內部類,JVM就不會去加載這個單例類,也就不會建立單例對象,從而實現懶漢式的延遲加載。也就是說這種方式能夠同時保證延遲加載和線程安全。