單例模式-雙重校驗鎖

/**
* 單例模式-雙重校驗鎖
* @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就不會去加載這個單例類,也就不會建立單例對象,從而實現懶漢式的延遲加載。也就是說這種方式能夠同時保證延遲加載和線程安全。

相關文章
相關標籤/搜索