標籤:線程 syn 管理 vat load effect 建構函式 ack 設計模型
一. 什麼是單例模式
只需要某個類同時保留一個對象,不希望有更多個物件,此時,我們則應考慮單例模式的設計。
單例模式的主要作用是保證在Java程式中,某個類只有一個執行個體存在。
單例模式有很多好處,它能夠避免執行個體對象的重複建立,不僅可以減少每次建立對象的時間開銷,還可以節約記憶體空間;
能夠避免由於操作多個執行個體導致的邏輯錯誤。如果一個對象有可能貫穿整個應用程式,而且起到了全域統一管理控制的作用,那麼單例模式也許是一個值得考慮的選擇。
二. 單例模式的特點
1. 單例模式只能有一個執行個體。
2. 單例類必須建立自己的唯一執行個體。
3. 單例類必須向其他對象提供這一執行個體。
三. 單例模式VS靜態類
在知道了什麼是單例模式後,我想你一定會想到靜態類,“既然只使用一個對象,為何不乾脆使用靜態類?”,這裡我會將單例模式和靜態類進行一個比較。
1. 單例可以繼承和被繼承,方法可以被override,而靜態方法不可以。
2. 靜態方法中產生的對象會在執行後被釋放,進而被GC清理,不會一直存在於記憶體中。
3. 靜態類會在第一次運行時初始化,單例模式可以有其他的選擇,即可以消極式載入。
4. 基於2, 3條,由於單例對象往往存在於DAO層(例如sessionFactory),如果反覆的初始化和釋放,則會佔用很多資源,而使用單例模式將其常駐於記憶體可以更加節約資源。
5. 靜態方法有更高的訪問效率。
6. 單例模式很容易被測試。
幾個關於靜態類的誤解:
誤解一:靜態方法常駐記憶體而執行個體方法不是。
實際上,特殊編寫的執行個體方法可以常駐記憶體,而靜態方法需要不斷初始化和釋放。
誤解二:靜態方法在堆(heap)上,執行個體方法在棧(stack)上。
實際上,都是載入到特殊的不可寫的代碼記憶體地區中。
靜態類和單例模式情景的選擇:
情景一:不需要維持任何狀態,僅僅用於全域訪問,此時更適合使用靜態類。
情景二:需要維持一些特定的狀態,此時更適合使用單例模式。
單例模型的寫法1、餓漢模式
public class Singleton{ private static Singleton instance = new Singleton(); private Singleton(){} public static Singleton newInstance(){ return instance; }}
註解:初試化靜態instance建立一次。如果我們在Singleton類裡面寫一個靜態方法不需要建立執行個體,它仍然會早早的建立一次執行個體。而降低記憶體的使用率。
缺點:沒有lazy loading的效果,從而降低記憶體的使用率。
2、懶漢模式
public class Singleton{ private static Singleton instance = null; private Singleton(){} public static Singleton newInstance(){ if(null == instance){ instance = new Singleton(); } return instance; }}
註解:Singleton的靜態屬性instance中,只有instance為null的時候才建立一個執行個體,建構函式私人,確保每次都只建立一個,避免重複建立。
缺點:只在單線程的情況下正常運行,在多線程的情況下,就會出問題。例如:當兩個線程同時運行到判斷instance是否為空白的if語句,並且instance確實沒有建立好時,那麼兩個線程都會建立一個執行個體,因此需要加鎖解決線程同步問題,實現如下:
public class Singleton{ private static Singleton instance = null; private Singleton(){} public static synchronized Singleton newInstance(){ if(null == instance){ instance = new Singleton(); } return instance; }}3、雙重校正鎖
public class Singleton { private static Singleton instance = null; private Singleton(){} public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) {//2 instance = new Singleton(); } } } return instance; }}
註解:只有當instance為null時,需要擷取同步鎖,建立一次執行個體。當執行個體被建立,則無需試圖加鎖。
缺點:用雙重if判斷,複雜,容易出錯。
4、靜態內部類
public class Singleton{ private static class SingletonHolder{ public static Singleton instance = new Singleton(); } private Singleton(){} public static Singleton newInstance(){ return SingletonHolder.instance; }}
這種方式同樣利用了類載入機制來保證只建立一個instance執行個體。它與餓漢模式一樣,也是利用了類載入機制,因此不存在多線程並發的問題。不一樣的是,它是在內部類裡面去建立對象執行個體。這樣的話,只要應用中不使用內部類,JVM就不會去載入這個單例類,也就不會建立單例對象,從而實現懶漢式的消極式載入。也就是說這種方式可以同時保證消極式載入和安全執行緒。
5、枚舉
public enum Singleton{ instance; public void whateverMethod(){} }
上面提到的四種實現單例的方式都有共同的缺點:
1)需要額外的工作來實現序列化,否則每次還原序列化一個序列化的對象時都會建立一個新的執行個體。
2)可以使用反射強行調用私人構造器(如果要避免這種情況,可以修改構造器,讓它在建立第二個執行個體的時候拋異常)。
而枚舉類很好的解決了這兩個問題,使用枚舉除了安全執行緒和防止反射調用構造器之外,還提供了自動序列化機制,防止還原序列化的時候建立新的對象。因此,《Effective Java》作者推薦使用的方法。不過,在實際工作中,很少看見有人這麼寫。
總結
本文總結了五種Java中實現單例的方法,其中前兩種都不夠完美,雙重校正鎖和靜態內部類的方式可以解決大部分問題,平時工作中使用的最多的也是這兩種方式。枚舉方式雖然很完美的解決了各種問題,但是這種寫法多少讓人感覺有些生疏。個人的建議是,在沒有特殊需求的情況下,使用第三種和第四種方式實現單例模式。
【Java】設計模型-五種單例模型