多線程下雙重檢查鎖的問題及解決方法

 單例模式中有一種實現方式叫雙重檢查鎖,主要是爲了更好、更安全的實現單例功能。先來看一下該方法的核心代碼:  

[java] view plain copy
  1. <span style="font-size:18px;">public  class DoubleCheckedLocking{  
  2.   private static Instance instance;                   
  3.    
  4.   public static Instance getInstance(){               
  5.     if(instance ==null){                              
  6.       synchronized (DoubleCheckedLocking.class){      
  7.         if(instance ==null)                           
  8.           instance=new Instance();                  //①  
  9.       }  
  10.     }  
  11.     return instance;  
  12.   }  
  13. }</span>  
   表面上來看,在執行該代碼時,先判斷instance對象是否爲空,爲空時再進行初始化對象。即使是在多線程環境下,因爲使用了synchronized鎖進行代碼同步,該方法也僅僅創建一個實例對象。但是,從根本上來說,這樣寫還是存在一定問題的。

  問題源頭:

   上述代碼標號爲①的代碼功能是是創建實例對象,可以分解爲如下僞代碼步驟: 

[html] view plain copy
  1. <span style="font-size:18px;">memory = allocate() ;    //分配對象的內存空間  
  2. ctorInstance(memory);   //②初始化對象  
  3. instance=memory;        //③設置instance指向剛分配的內存地址</span>  

  其中②和③之間,在某些編譯器編譯時,可能出現重排序(主要是爲了代碼優化),此時的代碼如下:  

[java] view plain copy
  1. <span style="font-size:18px;">memory = allocate() ;    //分配對象的內存空間  
  2. instance=memory;        //③設置instance指向剛分配的內存地址  
  3. ctorInstance(memory);   //②初始化對象  
  4. </span>  
   單線程下執行時序圖如下:

 

   多線程下執行時序圖:

   

   由於單線程中遵守intra-thread semantics,從而能保證即使②和③交換順序後其最終結果不變。但是當在多線程情況下,線程B將看到一個還沒有被初始化的對象,此時將會出現問題。

   解決方案:

    1、不允許②和③進行重排序

    2、允許②和③進行重排序,但排序之後,不允許其他線程看到。


   基於volatile的解決方案

    對前面的雙重鎖實現的延遲初始化方案進行如下修改:   

[java] view plain copy
  1. <span style="font-size:18px;">public  class DoubleCheckedLocking{</span>  
  2. <span style="font-size:18px;">  private volatile static Instance instance;                   
  3.    
  4.   public static Instance getInstance(){               
  5.     if(instance ==null){                              
  6.       synchronized (DoubleCheckedLocking.class){      
  7.         if(instance ==null)                           
  8.           instance=new Instance();                  //用volatile修飾,不會再出現重排序  
  9.       }  
  10.     }  
  11.     return instance;  
  12.   }  
  13. }</span>  
   使用volatile修飾instance之後,之前的②和③之間的重排序將在多線程環境下被禁止,從而保證了線程安全執行。

   注意:這個解決方案需要JDK5或更高版本(因爲從JDK5開始使用新的JSR-133內存模型規範,這個規範增強了volatile的語義)


  基於類初始化的解決方案

   JVM在類的初始化階段(即在Class被加載後,且被線程使用之前),會執行類的初始化。在執行類的初始化期間,JVM會去獲取一個鎖。這個鎖可以同步多個線程對同一個類的初始化。基於這個特性,可以實現另一種線程安全的延遲初始化方案。    

[java] view plain copy
  1. <span style="font-size:18px;">public class InstanceFactory {  
  2.     private static class InstanceHolder {  
  3.         public static Instance instance = new Instance();  
  4.     }  
  5.   
  6.     public static Instance getInstance() {  
  7.         return InstanceHolder.instance ;  //這裏將導致InstanceHolder類被初始化  
  8.     }  
  9. }</span>  
   執行的示意圖:   


   該方案的實質是,允許②和③進行重排序,但不允許非構造線程(此處是B線程)“看到”這個重排序。


原鏈接:點擊

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章