線程安全的單例模式

轉自:http://blog.sina.com.cn/s/blog_75247c770100yxpb.htmlhtml

 

面試的時候,經常會被問到這樣一個問題:請您寫出一個單例模式(Singleton Pattern)吧。好吧,寫就寫,這還不容易。順手寫一個:面試

 1 //飢餓模式
 2 public final class EagerSingleton  
 3 {  
 4     private static EagerSingleton singObj = new EagerSingleton();  
 5   
 6     private EagerSingleton(){  
 7     }  
 8   
 9     public static EagerSingleton getSingleInstance(){  
10        return singObj;
11     }  
12 }  

     這種寫法就是所謂的飢餓模式,每一個對象在沒有使用以前就已經初始化了。這就可能帶來潛在的性能問題:若是這個對象很大呢?沒有使用這個對象以前,就把它加載到了內存中去是一種巨大的浪費。安全

 
  針對這種狀況,咱們能夠對以上的代碼進行改進,使用一種新的設計思想—— 延遲加載(Lazy-load Singleton)
 1 //懶漢模式
 2 public final class LazySingleton  
 3 {  
 4     private static LazySingleton singObj = null;  
 5   
 6     private LazySingleton(){  
 7     }  
 8   
 9     public static LazySingleton getSingleInstance(){  
10         if(null == singObj ) singObj = new LazySingleton();
11           return singObj;
12     }  
13 }  

     這種寫法就是所謂的懶漢模式。它使用了延遲加載來保證對象在沒有使用以前,是不會進行初始化的。併發

 
  可是,一般這個時候面試官又會提問新的問題來刁難一下。他會問:這種寫法線程安全嗎?回答必然是:不安全。這是由於在多個線程可能同時運行到第九行,判斷singObj爲null,因而同時進行了初始化。因此,這是面臨的問題是如何使得這個代碼線程安全?很簡單,在那個方法前面加一個Synchronized就OK了。
 1 //懶漢模式加Synchronized 
 2 public final class ThreadSafeSingleton  
 3 {  
 4     private static ThreadSafeSingleton singObj = null;  
 5   
 6     private ThreadSafeSingleton(){  
 7     }  
 8   
 9     public static Synchronized ThreadSafeSingleton getSingleInstance(){  
10         if(null == singObj ) singObj = new ThreadSafeSingleton();
11             return singObj;
12     }  
13 }  

 

   
 
   寫到這裏,面試官可能仍然會狡猾的看了你一眼,繼續刁難到:這個寫法有沒有什麼性能問題呢?答案確定是有的! 同步的代價必然會必定程度的使程序的併發度下降。那麼有沒有什麼方法,一方面是線程安全的,有能夠有很高的併發度呢?咱們觀察到,線程不安全的緣由實際上是在初始化對象的時候,因此,能夠想辦法把同步的粒度下降,只在初始化對象的時候進行同步。這裏有必要提出一種新的設計思想—— 雙重檢查鎖(Double-Checked Lock)。
 1 //雙重檢查鎖
 2 public final class DoubleCheckedSingleton  
 3 {  
 4     private static DoubleCheckedSingletonsingObj = null;  
 5   
 6     private DoubleCheckedSingleton(){  
 7     }  
 8   
 9     public static DoubleCheckedSingleton getSingleInstance(){  
10         if(null == singObj ) {
11               Synchronized(DoubleCheckedSingleton.class){
12                      if(null == singObj)
13                            singObj = new DoubleCheckedSingleton();
14               }
15          }
16        return singObj;
17     }  
18 }  

      這種寫法使得只有在加載新的對象進行同步,在加載完了以後,其餘線程在第九行就能夠判斷跳過鎖的的代價直接到第15行代碼了。作到很好的併發度。性能

 
 
     至此,上面的寫法一方面實現了Lazy-Load,另外一個方面也作到了併發度很好的線程安全,一切看上很完美。這是,面試官可能會對你的回答滿意的點點頭。可是,你此時提出說,其實這種寫法仍是有問題的!!問題在哪裏?假設線程A執行到了第9行,它判斷對象爲空,因而線程A執行到第12行去初始化這個對象,但初始化是須要耗費時間的,可是這個對象的地址其實已經存在了。此時線程B也執行到了第九行,它判斷不爲空,因而直接跳到15行獲得了這個對象。可是,這個對象還 沒有被完整的初始化!獲得一個沒有初始化徹底的對象有什麼用!!關於這個Double-Checked Lock的討論有不少,目前公認這是一個Anti-Pattern,不推薦使用!因此當你的面試官聽到你的這番答覆,他會不會被Hold住呢?
     那麼有沒有什麼更好的寫法呢?有!這裏又要提出一種新的模式—— Initialization on Demand Holder.   這種方法使用內部類來作到延遲加載對象,在初始化這個內部類的時候,JLS(Java Language Sepcification)會保證這個類的線程安全。這種寫法最大的美在於,徹底使用了Java虛擬機的機制進行同步保證,沒有一個同步的關鍵字。
 1 //Initialization on Demand Holder
 2 public class Singleton    
 3 {    
 4     private static class SingletonHolder    
 5     {    
 6         public final static Singleton instance = new Singleton();    
 7     }    
 8    
 9     public static Singleton getInstance()    
10     {    
11         return SingletonHolder.instance;    
12     }    
13 }  
相關文章
相關標籤/搜索