單例模式(Singleton Pattern)的6種實現
摘要
在我們日常的工作中經常需要在應用程式中保持一個唯一的例項,如:IO處理,資料庫操作等,由於這些物件都要佔用重要的系統資源,所以我們必須限制這些例項的建立或始終使用一個公用的例項,這就是我們今天要介紹的——單例模式(Singleton Pattern)。
概念
單例模式(Singleton Pattern)是 Java 中最簡單的設計模式之一。 單例模式,也叫單子模式,是一種常用的軟體設計模式。在應用這個模式時,單例物件的類必須保證只有一個例項存在。許多時候整個系統只需要擁有一個的全域性物件,這樣有利於我們協調系統整體的行為。比如在某個伺服器程式中,該伺服器的配置資訊存放在一個檔案中,這些配置資料由一個單例物件統一讀取,然後服務程序中的其他物件再通過這個單例物件獲取這些配置資訊。這種方式簡化了在複雜環境下的配置管理(維基百科)。
用途
單例模式有一下特點: 1、單例類只能有一個例項。 2、單例類必須自己建立自己的唯一例項。 3、單例類必須給所有其他物件提供這一例項。 單例模式確保某個類只有一個例項,而且自行例項化並向整個系統提供這個例項。在計算機系統中,執行緒池、快取、日誌物件、對話方塊、印表機、顯示卡的驅動程式物件常被設計成單例。這些應用都或多或少具有資源管理器的功能。每臺計算機可以有若干個印表機,但只能有一個Printer Spooler,以避免兩個列印作業同時輸出到印表機中。每臺計算機可以有若干通訊埠,系統應當集中管理這些通訊埠,以避免一個通訊埠同時被兩個請求同時呼叫。總之,選擇單例模式就是為了避免不一致狀態,避免政出多頭。
實現方式
我們知道,一個類的物件的產生是由類建構函式來完成的。如果一個類對外提供了public的構造方法,那麼外界就可以任意建立該類的物件。所以,如果想限制物件的產生,一個辦法就是將構造方法變為私有的(至少是受保護的),使外面的類不能通過引用來產生物件。同時為了保證類的可用性,就必須提供一個自己的物件以及訪問這個物件的靜態方法。
1.懶漢模式,執行緒不安全
是否 Lazy 初始化:是 是否多執行緒安全:否 實現難度:易 描述:這種方式是最基本的實現方式,這種實現最大的問題就是不支援多執行緒。因為沒有加鎖 synchronized,所以嚴格意義上它並不算單例模式。 這種方式 lazy loading 很明顯,不要求執行緒安全,在多執行緒不能正常工作。
//當多個執行緒並行呼叫getInstance方法時,就會建立多個例項。 public class Singleton01 { private static Singleton01 instance = null; private Singleton01(){} public static Singleton01 getInstance(){ if (instance == null) { instance = new Singleton01(); } return instance; } }
2.懶漢式,執行緒安全
是否 Lazy 初始化:是 是否多執行緒安全:是 實現難度:易 描述:這種方式具備很好的 lazy loading,能夠在多執行緒中很好的工作,但是,效率很低,99% 情況下不需要同步。 優點:第一次呼叫才初始化,避免記憶體浪費。 缺點:必須加鎖 synchronized 才能保證單例,但加鎖會影響效率。 getInstance() 的效能對應用程式不是很關鍵(該方法使用不太頻繁)。
public class Singleton02 { private static Singleton02 instance = null; private Singleton02(){} public static synchronized Singleton02 getInstance(){ if (instance == null) { instance = new Singleton02(); } return instance; } }
3.雙重檢驗鎖模式
JDK 版本:JDK1.5 起 是否 Lazy 初始化:是 是否多執行緒安全:是 實現難度:較複雜 描述:這種方式稱為雙重檢查鎖(Double-Check Locking),需要注意的是,如果使用雙重檢查鎖定來實現懶漢式單例類,需要在靜態成員變數instance之前增加修飾符volatile,被volatile修飾的成員變數可以確保多個執行緒都能夠正確處理,且該程式碼只能在JDK 1.5及以上版本中才能正確執行。由於volatile關鍵字會遮蔽Java虛擬機器所做的一些程式碼優化,可能會導致系統執行效率降低,因此即使使用雙重檢查鎖定來實現單例模式也不是一種完美的實現方式。
public class Singleton03 { private volatile static Singleton03 instance; private Singleton03(){} public static Singleton03 getInstance(){ if (instance == null) { synchronized(Singleton03.class){ if (instance == null) { instance = new Singleton03(); } } } return instance; } }
4.餓漢式,static final field
是否 Lazy 初始化:否 是否多執行緒安全:是 實現難度:易 描述:這種方式比較常用,但容易產生垃圾物件。 優點:沒有加鎖,執行效率會提高。 缺點:類載入時就初始化,浪費記憶體。 它基於classloder機制避免了多執行緒的同步問題,不過,instance在類裝載時就例項化,雖然導致類裝載的原因有很多種,在單例模式中大多數都是呼叫getInstance方法, 但是也不能確定有其他的方式(或者其他的靜態方法)導致類裝載,這時候初始化instance顯然沒有達到lazy loading的效果。
public class Singleton04 { //在類載入時就初始化 private static final Singleton04 instance = new Singleton04(); private Singleton04(){} public static Singleton04 getInstance(){ return instance; } }
5.靜態內部類 static nested class
是否 Lazy 初始化:是 是否多執行緒安全:是 實現難度:一般 這種寫法仍然使用JVM本身機制保證了執行緒安全問題;由於 SingletonHolder 是私有的,除了 getInstance() 之外沒有辦法訪問它,因此它是懶漢式的;同時讀取例項的時候不會進行同步,沒有效能缺陷;也不依賴 JDK 版本。
public class Singleton05 { private static class SingletonHolder{ private static final Singleton05 INSTANCE = new Singleton05(); } private Singleton05(){} public static final Singleton05 getInstance(){ return Single6.tonHolder.INSTANCE; } }
6.列舉
是否 Lazy 初始化:否 是否多執行緒安全:是 實現難度:易 描述:這種實現方式還沒有被廣泛採用,但這是實現單例模式的最佳方法。它更簡潔,自動支援序列化機制,絕對防止多次例項化。 這種方式是Effective Java作者Josh Bloch提倡的方式,它不僅能避免多執行緒同步問題,而且還自動支援序列化機制,防止反序列化重新建立新的物件,絕對防止多次例項化。不過,由於 JDK1.5 之後才加入 enum 特性,用這種方式寫不免讓人感覺生疏,在實際工作中,也很少用。 不能通過reflection attack來呼叫私有構造方法。
public enum Singleton06 { INSTANCE; }