單例模式-雙重校驗鎖


/**
* 單例模式-雙重校驗鎖
* @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();
             }

       }

     }

 

注意事項:
問題:為什么需要兩次判斷if(singleTon==null)?

  分析:第一次校驗:由於單例模式只需要創建一次實例,如果后面再次調用getInstance方法時,則直接返回之前創建的實例,因此大部分時間不需要執行同步方法里面的代碼,大大提高了性能。如果不加第一次校驗的話,那跟上面的懶漢模式沒什么區別,每次都要去競爭鎖。

     第二次校驗:如果沒有第二次校驗,假設線程t1執行了第一次校驗后,判斷為null,這時t2也獲取了CPU執行權,也執行了第一次校驗,判斷也為null。接下來t2獲得鎖,創建實例。這時t1又獲得CPU執行權,由於之前已經進行了第一次校驗,結果為null(不會再次判斷),獲得鎖后,直接創建實例。結果就會導致創建多個實例。所以需要在同步代碼里面進行第二次校驗,如果實例為空,則進行創建。

  需要注意的是,private static volatile SingleTon3 singleTon=null;需要加volatile關鍵字,否則會出現錯誤。問題的原因在於JVM指令重排優化的存在。在某個線程創建單例對象時,在構造方法被調用之前,就為該對象分配了內存空間並將對象的字段設置為默認值。此時就可以將分配的內存地址賦值給instance字段了,然而該對象可能還沒有初始化。若緊接着另外一個線程來調用getInstance,取到的就是狀態不正確的對象,程序就會出錯。

  (4)靜態內部類:同樣也是利用了類的加載機制,它與餓漢模式不同的是,它是在內部類里面去創建對象實例。這樣的話,只要應用中不使用內部類,JVM就不會去加載這個單例類,也就不會創建單例對象,從而實現懶漢式的延遲加載。也就是說這種方式可以同時保證延遲加載和線程安全。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM