ava單例模式的正確寫法

一、懶漢式(線程不安全)

 

Java代碼  收藏代碼
  1. public class Singleton {  
  2.     private static Singleton instance;  
  3.     private Singleton (){}  
  4.   
  5.     public static Singleton getInstance() {  
  6.      if (instance == null) {  
  7.          instance = new Singleton();  
  8.      }  
  9.      return instance;  
  10.     }  
  11. }  

 

介紹:線程不安全,在多線程情況下容易創建多個實例。

 

 

二、懶漢式(線程安全)

 

Java代碼  收藏代碼
  1. public static synchronized Singleton getInstance() {  
  2.     if (instance == null) {  
  3.         instance = new Singleton();  
  4.     }  
  5.     return instance;  
  6. }  

  

介紹:雖然線程安全,但是不夠高效。

 

 

三、雙重檢驗鎖

 

Java代碼  收藏代碼
  1. public static Singleton getSingleton() {  
  2.     if (instance == null) {                         //Single Checked  
  3.         synchronized (Singleton.class) {  
  4.             if (instance == null) {                 //Double Checked  
  5.                 instance = new Singleton();  
  6.             }  
  7.         }  
  8.     }  
  9.     return instance ;  
  10. }  

 

介紹雙重檢驗鎖機制比懶漢式(線程安全)要高效很多,因爲它不用對instance不爲空的情況進行同步。這段代碼看起來似乎很完美,但很可惜,還是存在問題。instance=new Singleton() 這並非是一個原子性質的操作,事實上在JVM中做了如下三件事:

 

  1. 給instance分配內存
  2. 調用構造函數初始化成員變量
  3. 將instance對象指向分配的內存(此步驟完成instance即爲非空)

但是在 JVM 的即時編譯器中存在指令重排序的優化。也就是說上面的第二步和第三步的順序是不能保證的,最終的執行順序可能是 1-2-3 也可能是 1-3-2。如果是後者,則在 3 執行完畢、2 未執行之前,被線程二搶佔了,這時 instance 已經是非 null 了(但卻沒有初始化),所以線程二會直接返回 instance,然後使用,然後順理成章地報錯。

 

四、雙重檢驗鎖(volatile)

 

Java代碼  收藏代碼
  1. public class Singleton {  
  2.     private volatile static Singleton instance; //聲明成 volatile  
  3.     private Singleton (){}  
  4.   
  5.     public static Singleton getSingleton() {  
  6.         if (instance == null) {                           
  7.             synchronized (Singleton.class) {  
  8.                 if (instance == null) {         
  9.                     instance = new Singleton();  
  10.                 }  
  11.             }  
  12.         }  
  13.         return instance;  
  14.     }  
  15.      
  16. }  

 

 介紹:使用 volatile 的原因是可以保證線程在本地不會存有 instance 的副本,每次都是去主內存中讀取。這裏使用volatile的一個主要原因就是volatile可以禁止JVM指令重排序。

 

五、餓漢式

 

Java代碼  收藏代碼
  1. public class Singleton{  
  2.     //類加載時就初始化  
  3.     private static final Singleton instance = new Singleton();  
  4.       
  5.     private Singleton(){}  
  6.   
  7.     public static Singleton getInstance(){  
  8.         return instance;  
  9.     }  
  10. }  

 

 介紹:這種方法非常簡單,因爲單例的實例被聲明成 static 和 final 變量了,在第一次加載類到內存中時就會初始化,所以創建實例本身是線程安全的。缺點是它不是一種懶加載模式。比如 Singleton 實例的創建是依賴參數或者配置文件的,在 getInstance() 之前必須調用某個方法設置參數給它,那樣這種單例寫法就無法使用了。

 

五、靜態內部類

 

Java代碼  收藏代碼
  1. public class Singleton {    
  2.     private static class SingletonHolder {    
  3.         private static final Singleton INSTANCE = new Singleton();    
  4.     }    
  5.     private Singleton (){}    
  6.     public static final Singleton getInstance() {    
  7.         return SingletonHolder.INSTANCE;   
  8.     }    
  9. }  

 

 介紹:這種寫法仍然使用JVM本身機制保證了線程安全問題;由於 SingletonHolder 是私有的,除了 getInstance() 之外沒有辦法訪問它,因此它是懶漢式的;同時讀取實例的時候不會進行同步,沒有性能缺陷;也不依賴 JDK 版本。

 

六、枚舉

 

Java代碼  收藏代碼
  1. public enum EasySingleton {  
  2.       
  3.     INSTANCE;  
  4.       
  5.     EasySingleton(){  
  6.           
  7.     }  
  8.       
  9. }  

 

介紹:我們可以通過EasySingleton.INSTANCE來訪問實例,這比調用getInstance()方法簡單多了。創建枚舉默認就是線程安全的,所以不需要擔心double checked locking,而且還能防止反序列化導致重新創建新的對象。

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