單例模式(Singleton Pattern)是 編程語言中最簡單的設計模式之一。這種類型的設計模式屬于創建型模式,它提供了一種創建對象的最佳方式。
? ? 單例模式的意圖在于:保證一個類僅有一個實例,并提供一個訪問它的全局訪問點。
? ? 何時使用:當您想控制實例數目,節省系統資源的時候。
? ??使用場景:?
? ? ? 1、要求生產唯一序列號。
? ? ? 2、WEB 中的計數器,不用每次刷新都在數據庫里加一次,用單例先緩存起來。?
? ? ? 3、創建的一個對象需要消耗的資源過多,比如 I/O 與數據庫的連接等。
? ? 優點:?
? ? ? 1、在內存里只有一個實例,減少了內存的開銷,尤其是頻繁的創建和銷毀實例(比如管理學院首頁頁面緩存)。?
? ? ? 2、避免對資源的多重占用(比如寫文件操作)。?
? ? 缺點:沒有接口,不能繼承,與單一職責原則沖突,一個類應該只關心內部邏輯,而不關心外面怎么樣來實例化。
為了更好便于理解代碼的運行機制,請先耐住性子看完以下的知識點:
涉及知識點:線程鎖
? ? ? ReentrantLock 擁有Synchronized相同的并發性和內存語義,此外還多了 鎖投票,定時鎖等候和中斷鎖等候線程A和B都要獲取對象O的鎖定,假設A獲取了對象O鎖,B將等待A釋放對O的鎖定,
? ? ? 如果使用 synchronized ,如果A不釋放,B將一直等下去,不能被中斷如果 使用ReentrantLock,如果A不釋放,可以使B在等待了足夠長的時間以后,中斷等待,而干別的事情?
設計知識點:靜態成員|靜態方法|靜態構造函數
一、靜態成員(static)
(1) 靜態成員屬于類所有,非靜態成員屬于類的實例所有。
(2) 每創建一個類的實例,都會在內存中為非靜態成員新分配一塊存儲;非靜態成員屬于類所有,為各個類的實例所公用,無論類創建了多少實例,類的靜態成員在內存中只占同一塊區域。
二、靜態方法
(1) 靜態方法屬于類所有,類實例化前即可使用。
(2) 非靜態方法可以訪問類中的任何成員,靜態方法只能訪問類中的靜態成員。因為靜態方法在類實例化前就可以使用,而類中的非靜態變量必須在實例化之后才能分配內存,這樣,靜態方法調用時無法判斷非靜態變量使用的內存地址。所以無法使用.而靜態變量的地址對類來說是固定的,故可以使用。
三、靜態構造函數
(1) 用于對靜態字段、只讀字段等的初始化。
(2) 添加static關鍵字,不能添加訪問修飾符,因為靜態構造函數都是 ?私自有的。
(3) 類的靜態構造函數在給定應用程序域中至多執行一次:只有創建 類的實例或者引用類任何靜態成員才激發靜態構造函數 .
(4) 靜態構造函數是不可繼承的,而且不能被直接調用。
(5) 如果類中包含用來開始執行的 Main 方法,則該類的靜態構造函 數將在調用 Main 方法之前執行。任何帶有初始值設定項的靜態字段,則在執行該類的靜態構造函數時,先要按照文本順序執行那些初始值設定項。
(6) 如果沒有編寫靜態構造函數,而這時類中包含帶有初始值設定的靜態字段,那么編譯器會自動生成默認的靜態構造函數。
(7) 無參數的構造函數可以與靜態構造函數共存。盡管參數列表相同, 但一個屬于類,一個屬于實例,所以不會沖突。 ?MSDN對靜態構造函數的解釋: ? 靜態構造函數用于初始化任何靜態數據,或用于執行僅需執行一次的特定操作。在創建第一個實例或引用任何靜態成員之前,將自動調用靜態構造函數。
靜態構造函數具有以下特點:
靜態構造函數既沒有訪問修飾符,也沒有參數。
如何使用:
? ? 第一步:?創建一個 Singleton 類。
publicclassSingleObject
{
? ? //創建 SingleObject 的一個對象
? ? privatestaticSingleObjectinstance=newSingleObject();
? ? //讓構造函數為 private,這樣該類就不會被實例化
? ? privateSingleObject(){}
? ? //獲取唯一可用的對象
? ? publicstaticSingleObjectgetInstance()
? ? {
? ? ? ? returninstance;
? ? }
? ? publicvoidshowMessage()
? ? {
? ? ? ? System.out.println("Hello World!");
? ? }
}
? ? 第二步:從 singleton 類獲取唯一的對象。
publicclassSingletonPatternDemo
{
? ? publicstaticvoidmain(String[]args)
? ? {
? ? ? ? //不合法的構造函數
? ? ? ? //編譯時錯誤:構造函數 SingleObject() 是不可見的
? ? ? ?//SingleObject object = new SingleObject();
? ? ? ? //獲取唯一可用的對象
? ? ? ? SingleObjectobject=SingleObject.getInstance();
? ? ? ? //顯示消息object.showMessage();
? ? }
}
? ? 第三部:驗證輸出。
? ? Hello World!
單例模式的幾種實現方式
單例模式的實現有多種方式,如下所示:
1、懶漢式,線程不安全
是否 Lazy 初始化:是
是否多線程安全:否
實現難度:易
描述:這種方式是最基本的實現方式,這種實現最大的問題就是不支持多線程。因為沒有加鎖 synchronized,所以嚴格意義上它并不算單例模式。
這種方式 lazy loading 很明顯,不要求線程安全,在多線程不能正常工作。
代碼實例:
public class Singleton
?{
? ? private static Singleton instance;
? ? private Singleton (){}
? ? public static Singleton getInstance() {
? ? if (instance == null)?
? ? {
? ? ? ? instance = new Singleton();
? ? }
? ? return instance;
? ? }
}
接下來介紹的幾種實現方式都支持多線程,但是在性能上有所差異。
2、懶漢式,線程安全
是否 Lazy 初始化:是
是否多線程安全:是
實現難度:易
描述:這種方式具備很好的 lazy loading,能夠在多線程中很好的工作,但是,效率很低,99% 情況下不需要同步。
優點:第一次調用才初始化,避免內存浪費。
缺點:必須加鎖 synchronized 才能保證單例,但加鎖會影響效率。
getInstance() 的性能對應用程序不是很關鍵(該方法使用不太頻繁)。
涉及知識點:線程鎖
代碼實例:
public class Singleton
?{
? ? private static Singleton instance;
? ? private Singleton (){}
? ? public static synchronized Singleton getInstance() {
? ? if (instance == null)
? ? {
? ? ? ? instance = new Singleton();
? ? }
? ? return instance;
? ? }
}
3、餓漢式
是否 Lazy 初始化:否
是否多線程安全:是
實現難度:易
描述:這種方式比較常用,但容易產生垃圾對象。
優點:沒有加鎖,執行效率會提高。
缺點:類加載時就初始化,浪費內存。
它基于 classloder 機制避免了多線程的同步問題,不過,instance 在類裝載時就實例化,雖然導致類裝載的原因有很多種,在單例模式中大多數都是調用 getInstance 方法, 但是也不能確定有其他的方式(或者其他的靜態方法)導致類裝載,這時候初始化 instance 顯然沒有達到 lazy loading 的效果。
代碼實例:
public class Singleton
?{
? ? private static Singleton instance = new Singleton();
? ? private Singleton (){}
? ? public static Singleton getInstance()
? ? {
? ? ? ? return instance;
? ? }
}
4、雙檢鎖/雙重校驗鎖(DCL,即 double-checked locking)
JDK 版本:JDK1.5 起
是否 Lazy 初始化:是
是否多線程安全:是
實現難度:較復雜
描述:這種方式采用雙鎖機制,安全且在多線程情況下能保持高性能。
getInstance() 的性能對應用程序很關鍵。
代碼實例:
public class Singleton
?{
? ? private volatile static Singleton singleton;
? ? private Singleton (){}
? ? public static Singleton getSingleton()
? ? {
? ? ? ? if (singleton == null)
? ? ? ? {
? ? ? ? ? ? synchronized (Singleton.class)
? ? ? ? ? ? ?{
? ? ? ? ? ? ? ? ? ?if (singleton == null)
? ? ? ? ? ? ? ? ? ?{
? ? ? ? ? ? ? ? ? ? ? singleton = new Singleton();
? ? ? ? ? ? ? ? ? ?}
? ? ? ? ? ? ?}
? ? ? ? }
? ? return singleton;
? ?}
}
5、登記式/靜態內部類
是否 Lazy 初始化:是
是否多線程安全:是
實現難度:一般
描述:這種方式能達到雙檢鎖方式一樣的功效,但實現更簡單。對靜態域使用延遲初始化,應使用這種方式而不是雙檢鎖方式。這種方式只適用于靜態域的情況,雙檢鎖方式可在實例域需要延遲初始化時使用。
這種方式同樣利用了 classloder 機制來保證初始化 instance 時只有一個線程,它跟第 3 種方式不同的是:第 3 種方式只要 Singleton 類被裝載了,那么 instance 就會被實例化(沒有達到 lazy loading 效果),而這種方式是 Singleton 類被裝載了,instance 不一定被初始化。因為 SingletonHolder 類沒有被主動使用,只有顯示通過調用 getInstance 方法時,才會顯示裝載 SingletonHolder 類,從而實例化 instance。想象一下,如果實例化 instance 很消耗資源,所以想讓它延遲加載,另外一方面,又不希望在 Singleton 類加載時就實例化,因為不能確保 Singleton 類還可能在其他的地方被主動使用從而被加載,那么這個時候實例化 instance 顯然是不合適的。這個時候,這種方式相比第 3 種方式就顯得很合理。
代碼實例:
public class Singleton
?{
? ? private static class SingletonHolder?
? ? {
? ? ? ? private static final Singleton INSTANCE = new Singleton();
? ? }
? ? private Singleton (){}
? ? public static final Singleton getInstance() {
? ? return SingletonHolder.INSTANCE;
? ? }
}
6、枚舉
JDK 版本:JDK1.5 起
是否 Lazy 初始化:否
是否多線程安全:是
實現難度:易
描述:這種實現方式還沒有被廣泛采用,但這是實現單例模式的最佳方法。它更簡潔,自動支持序列化機制,絕對防止多次實例化。
這種方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不僅能避免多線程同步問題,而且還自動支持序列化機制,防止反序列化重新創建新的對象,絕對防止多次實例化。不過,由于 JDK1.5 之后才加入 enum 特性,用這種方式寫不免讓人感覺生疏,在實際工作中,也很少用。
不能通過 reflection attack 來調用私有構造方法。
代碼實例:
public enum Singleton?
{
? ? INSTANCE;
? ? public void whateverMethod()
? ? {
? ? }
}
經驗之談:一般情況下,不建議使用第 1 種和第 2 種懶漢方式,建議使用第 3 種餓漢方式。只有在要明確實現 lazy loading 效果時,才會使用第 5 種登記方式。如果涉及到反序列化創建對象時,可以嘗試使用第 6 種枚舉方式。如果有其他特殊的需求,可以考慮使用第 4 種雙檢鎖方式。