再孬再好,就你一個
單例模式
單例模式(Singleton Pattern)是 Java 中最簡單的設計模式之一。這種類型的設計模式屬於創建型模式,它提供了一種創建對象的最佳方式。
這種模式涉及到一個單一的類,該類負責創建自己的對象,同時確保只有單個對象被創建。這個類提供了一種訪問其唯一的對象的方式,可以直接訪問,不需要實例化該類的對象。
注意:
-
單例類限制類的實例個數,保證類的實例在JVM的世界里只有一個類的實例對象。
- 單例類必須自己創建自己的唯一實例。
-
單例類必須提供一個全局性的公共訪問方式獲取單例類的實例對象
-
單例類必須給所有其他對象提供這一實例。
介紹
意圖:保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。
主要解決:一個全局使用的類頻繁地創建與銷毀。
何時使用:當您想控制實例數目,節省系統資源的時候。
如何解決:判斷系統是否已經有這個單例,如果有則返回,如果沒有則創建。
關鍵代碼:構造函數是私有的。
應用場景: java.lang.Runtime類、日志、驅動器、緩存、線程池等l
實現
我們將創建一個 SingleObject 類。SingleObject 類有它的私有構造函數和本身的一個靜態實例。
SingleObject 類提供了一個靜態方法,供外界獲取它的靜態實例。SingletonPatternDemo,我們的演示類使用 SingleObject 類來獲取 SingleObject 對象。
步驟 1
創建一個 Singleton 類,SingleObject.java
步驟 2
從 singleton 類獲取唯一的對象,測試類SingletonPatternDemo.java
輸出效果:
不過java實現單例模式有好幾種方式,簡單繪圖一張https://www.processon.com/view/link/5ebb9c087d9c08156c3be39a,如下:
我就一一列舉:
1. 餓漢式初始化 Eager initialization
在餓漢初始化模式下,單例類的實例是在類加載的時候完成了創建,這是最簡單的創建單例的方式,但它有個缺陷就是客戶端還沒使用已創建好的單例實例。下面就是實現了靜態初始化的單例類
2. 靜態塊初始化 Static block initialization
靜態塊初始化的實現單例和餓漢式初始化類似,但它在創建類實例的時候提供了異常處理
3. 延遲初始化 Lazy Initialization
延遲初始化意味着創建單例的是時機不是在類加載過程了,而是在全局訪問方法里實現單例模式的實例創建,就是需要時再創建
此種實現方式在單線程環境下表現很好,但是現實中往往是多線程環境,故有了下面的其他實現方式
4. 線程安全型單例Thread Safe Singleton
有一種簡單創建線程安全的單例,就是給全局訪問方法加上關鍵字synchronized (鎖的一種),它可以確保只有一個線程執行獲取單例的全局訪問方法。
Above implementation works fine and provides thread-safety but it reduces the performance because of the cost associated with the synchronized method, although we need it only for the first few threads who might create the separate instances (Read: Java Synchronization). To avoid this extra overhead every time, double checked locking principle is used. In this approach, the synchronized block is used inside the if condition with an additional check to ensure that only one instance of a singleton class is created.
該實現確實實現了線程安全的單例,可同步方法對性能有影響,可改造為雙重檢測鎖
5. 內部靜態輔助類 Inner Static Helper Class
借助內部靜態類創建單例
特別注意,當單例類被加載的時候,內部輔助類還沒有加載到內存,當有客戶端調用公共訪問getSingletonInstance方法時內部類才被加載,然后創建單例對象。
一句話:私有內部靜態類負責創建單例類的實例對象,且不需要同步機制
7. 序列化單例 Serialization and Singleton
有時在分布式系統中,為了在文件系統中存放單實例狀態,我們需要在單例類中實現序列化接口(Serializable interface),下面是一個實現了序列化接口的單實例類:
.序列化的單實例進行反序列化時會出現問題,創建了一個新的類實例對象,如下代碼:
輸出結果:
可以通過在單實例類的定義中加上
注意,反序列化會判斷是否有readResolve方法
這樣當JVM從內存中反序列化地"組裝"一個新對象時,就會自動調用這個 readResolve方法來返回我們指定好的對象了, 單例模式規則也就得到了保證.
此時非常好,JVM世界里只有一個單實例對象
8. 枚舉單例 Enum Singleton
用枚舉的方式實現單例模式也不失為一種好方法,它可以保證任何一個枚舉值只被實例化一次
測試枚舉型單例代碼:
輸出結果
9. 使用反射 ,魔方
可以使用反射,注意,它和沒有使用readResolve()方法的序列化單例模式類似,會破壞單例模式,JVM的世界里會有多個實例對象
執行輸出結果(好可怕破壞單例模式)
發現兩個實例並不相等,反射很強大,被廣泛用於像Spring、Hibernate、Mybatis等框架中,普通開發禁止使用。加上我的反射一文,從此以后,你可以光明正大的說,私有屬性、私有構造器、私有方法可以訪問兼修改了
小結: 單例模式實現多種多樣,實際場景自行把控選擇。代碼已進入github: https://github.com/dongguangming/design-pattern/tree/master/src/code/singleton
參考:
0. 單例模式 https://baike.baidu.com/item/%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F/5946627
1. Java Singleton Design Pattern Best Practices with Examples https://www.journaldev.com/1377/java-singleton-design-pattern-best-practices-examples
2. 要FQ https://javarevisited.blogspot.com/2012/12/how-to-create-thread-safe-singleton-in-java-example.html
3. double checked locking (同樣要FQ)http://jeremymanson.blogspot.com/2008/05/double-checked-locking.html
4. The "Double-Checked Locking is Broken" Declaration?
https://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html