深入探索Java設計模式之單例模式
單例模式可確保在給定的時間例項中只能建立一個具有全域性訪問點的物件。這是面向物件程式設計中最常用的技術之一。儘管它很簡單,但從類設計的角度來看可能是最簡單的,但是在嘗試實現它們之前,必須先解決一些細微的問題。本文是在學習完優銳課JAVA架構VIP課程—【框架原始碼專題】中《學習原始碼中的優秀設計模式》後寫下的學習感悟。通過引用Java程式碼示例來深入探索這種模式。
總覽
在某些情況下,系統應在給定的時間點僅允許一個類的物件儲存在記憶體中。這意味著,當程式例項化該物件時,不應允許該程式建立該類的其他物件。例如,在連線到資料庫的系統中,僅使用一個物件來管理資料庫連線。這樣可以確保其他物件無法初始化不必要的連線,從而由於多個例項化而使系統的整體效能下降。通過建立多個JDBC連線並觀察效能,可以很容易地對其進行測試。效能肯定會受到影響或顯著降低。單例模式實質上可以保證系統僅建立一個類的例項。
單例模式
使用單例模式是確保給定類例項化一個且只有一個物件的一種標準技術。這是“四人幫”討論的二十四個設計模式之一。想法是解決諸如例項化類的單個例項,訪問類的唯一例項或控制類例項化過程的問題。實現此目的的關鍵技術是通過使用私有修飾符隱藏建構函式,並提供一種應用檢查和驗證的方法,以確保僅建立該類的一個例項。
這很簡單,但有效地滿足了需求。實際上,通過應用相同的原理,我們可以控制建立類的例項的確切數目,而不僅限於單個例項。但是,這裡我們僅關注單個例項。
使用單例模式
不難發現實際上我們只需要一個類的一個例項在記憶體中的情況。多個物件可能會破壞原因或對正在執行的應用程式的效能造成破壞。在面向物件的程式設計中,正在執行的應用程式通常在記憶體中具有許多物件,它們在執行過程中相互作用並扮演著至關重要的角色。但是,在實現執行緒池,快取記憶體,對話方塊或負責首選項和登錄檔設定的物件時,資料庫連線,日誌記錄,充當外圍裝置或圖形卡裝置驅動程式的物件等都是我們想要的情況。物件執行的單個例項;否則,它將產生很多問題,例如資源阻塞,記憶體過載以及許多其他不一致的行為。這正是我們需要確定適合於實現單例模式的類並進行相應設計的地方。
實現單例模式
這是該模式的快速實現。
1 public final class Singleton { 2 private static final Singleton singleInstance = 3 new Singleton(); 4 private Singleton(){ 5 } 6 public static Singleton getInstance(){ 7 return singleInstance; 8 } 9 } 10 11 public class TestSingleton { 12 public static void main(String[] args){ 13 Singleton s1; 14 Singleton s2; 15 s1 = Singleton.getInstance(); 16 s2 = Singleton.getInstance(); 17 if(s1 == s2){ 18 System.out.println("References to same 19 Singleton object"); 20 } 21 } 22 }
Singleton類被宣告為final,因此無法建立任何子類。這甚至限制了多重例項化,即使通過對該類進行子類化。建構函式被宣告為私有;結果,只有Singleton類可以使用此建構函式建立Singleton物件。對Singleton物件的靜態引用將呼叫私有建構函式,並僅提供該類的一個例項。呼叫getInstance() 方法時,僅接收物件的參考副本。
在這裡,我們通過建立另一個稱為TestSingleton的類來使用一個簡單的測試。此類宣告兩個參考Singleton物件,並呼叫getInstance()方法。然後,我們比較這兩個引用,以確保它們實際上引用的是同一執行時例項,而不是兩個不同的物件。
有時,我們需要一個僅在第一個靜態方法被呼叫時才建立例項的實現,而不是在此之前。在前面的示例中,物件是靜態建立的;getInstance()方法的作用是簡單地返回引用。在這種情況下,我們希望在首次呼叫時建立物件,稱為延遲初始化。另外,我們希望呼叫是執行緒安全的;否則,可能導致奇怪的不穩定行為或記憶體洩漏,從而導致JVM崩潰。這是實現此目的的示例。
1 public final class Singleton { 2 private static volatile Singleton singleInstance = 3 null; 4 private Singleton(){ 5 } 6 public static Singleton getInstance(){ 7 if (singleInstance == null) { 8 synchronized(Singleton.class) { 9 if (singleInstance == null) { 10 singleInstance = new Singleton(); 11 } 12 } 13 } 14 return singleInstance; 15 } 16 } 17 18 public class TestSingleton { 19 public static void main(String[] args){ 20 Singleton s1; 21 Singleton s2; 22 s1 = Singleton.getInstance(); 23 s2 = Singleton.getInstance(); 24 if(s1 == s2){ 25 System.out.println("References to same 26 Singleton object"); 27 } 28 } 29 }
也可以使用Enum實現單例。實際上,在可能的情況下,最好使用Enum代替類來實現單例模式。JVM保證從單例Enum中只能建立一個例項。
1 public enum SingletonEnum { 2 SINGLEINSTANCE; 3 public void someMethod(){ 4 // ... 5 } 6 } 7 8 public class TestSingleton { 9 public static void main(String[] args){ 10 SingletonEnum s1; 11 SingletonEnum s2; 12 s1 = SingletonEnum.SINGLEINSTANCE; 13 s2 = SingletonEnum.SINGLEINSTANCE; 14 if (s1 == s2){ 15 System.out.println("References to same 16 Singleton object"); 17 } 18 } 19 }
結論
與往常一樣,使用者的自由裁量權是良好設計的關鍵,因為不當使用單例模式可能會導致其他問題。如果單例執行復雜且重量級的操作,則很難對其進行測試。更好的建議是使用依賴項注入框架來構造單個物件。在諸如在GUI應用程式中建立對話方塊或很少有併發使用者的情況下,單例效果最佳。單例模式雖然有用,但在大型可擴充套件應用程式中造成效能瓶頸方面臭名昭著。
感謝閱讀!歡迎留言。想更深入探討學習也歡迎私信我。下篇繼