單例模式(Singleton)
單例模式
意圖
單例模式用來保證一個類只有一個例項, 這個例項不是外界手動
new
出來的, 而是對外提供一個方法來訪問到它.單例模式封裝了初始化方法, 保證這個例項只被初始化一次.
要解決什麼問題
有時候程式需要一個唯一的物件, 它在整個程式的宣告週期中只存在一個例項. 比如:
Logger. 很多情況下. 我們希望所有物件都往一個檔案中寫
log
. 在建立Logger
的時候, 假如我們每次都用:Logger logger = new Logger(“dir/log.file”);
那麼我們有可能會出現日誌檔案會被重新清空(開啟檔案時候選擇, 清空開啟), 甚至使得日誌檔案內容錯亂(假如多個執行緒同時呼叫 log 方法).
- Factory. 很多工廠只用初始化一次就夠了.
- Service. 有的 Service 可能初始化比較複雜, 但是功能知識相當於一個 Delegate, 這種情況下, Service 做成可以做成單例.
討論
基本思路
通常情況下, 想要獲得一個類的一個例項, 無非就是 new
出來. 只要有對建構函式的訪問許可權, 我們想要多少個例項就能 new
多少個例項. 那麼, 我們怎麼才能做到讓一個類只能有一個例項呢?
答案就是: 控制訪問許可權.
因為, 不管 public
也好 protected
也好, package
也好, 只要有一定的可見性, 建立的權利就完全交給外界了. 只有設定為 private
, 限制了構造方法可見性, 也就控制了外界建立物件的權利.
可是, 將構造方法設定為 private
, 外界的確無法建立了, 那麼這個類還有什麼用? 例項都建立不了呀!
其實, 雖然外界建立不了這個例項, 但是類本身還是可以訪問自己的建構函式從而建立例項出來. 我們用一個 field 儲存一個例項, 然後我們對外提供一個 public
的方法, 以便外界通過這個方法訪問這個例項. 現在, 我們來想象一下這個類的樣子:
public class A {
private A instance = new A();
private A() {
}
public A getInstance() {
return instance;
}
}
這裡有個問題, 這個 getInstance()
方法該怎麼呼叫? 這個方法必須要有例項才能呼叫, 可是我們這個方法就是用來建立例項的. 該怎麼辦? 有沒有一種方法能不讓我們建立例項就可以呼叫類的方法的?
靜態方法! 只需要這個類被載入且被初始化就能通過 類名.靜態方法名()
就可以呼叫到. 好! 現在我們來修改一下啊這個類:
public class A {
// 因為靜態方法只能訪問靜態變數, 所以要把 instance 設為靜態以供 getInstance() 訪問
private static A instance = new A();
private A() {
}
public static A getInstance() {
return instance;
}
}
延時載入
現在, 讓我們捋一捋這個例項是如何建立的. 我們有一個 A
型別的field. 這個filed會在類完成裝載,連結後在初始化階段(靜態初始化器 static initializer)被賦值. 之後, 不管有沒有呼叫類的 getInstance()
方法,都會存在於應用程式中. 這裡又有問題了, 不管用沒用到 getInstance()
方法, 這個 field 所代表的例項一定會被建立! 那初始化過程中虛擬機器任務是多麼繁重? 我們能不能改成用到的時候才初始化? 答案是可以的, 我們可以將這個例項的建立延時成第一次呼叫 getInstance()
時:
public class A {
// 1. 因為靜態方法只能訪問靜態變數, 所以要把 instance 設為靜態以供 getInstance() 訪問
// 2. 設為 null
private static A instance = null
private A() {
}
public static A getInstance() {
// 第一次呼叫 getInstance() 時, instance 一定為 null, 這時候要進行例項的建立
if (instance == null) {
instance = new A();
}
// instance 除了第一次之外, 都不為null, 可以直接返回.
return instance;
}
}
多執行緒問題
之前, 我們使用
private static A instance = new A();
這種方式的時候, 我們是不需要考慮多執行緒情況的, 因為 VM 會處理的很好. 但是現在放在了 getInstance()
裡, 就需要我們自己控制多執行緒併發問題了.
試想一下, 假如有多個執行緒同時呼叫 getInstance()
方法會怎樣? 當程式碼執行到
if (instance == null) {
instance = new A();
}
時候, 第一個執行緒判斷 instance
為 null
, 然後打算執行 instance = new A()
, 但是沒有真的執行, instance
仍為 null
; 然而在這時, 時間片用光了, 假設輪到第二個執行緒執行, 同樣判斷 instance
為 null
, 執行了 instance = new A()
, 這時第二個時間片用完了, 有可能切回第一個執行緒, 這時, 第一個執行緒繼續執行 instance = new A()
. 問題就來了, getInstance()
為這兩個執行緒返回了兩個不同的例項! 這不是我們想要的!
好在對於執行緒安全問題,我們早已經有很成熟的解決方法, 就是關鍵字 synchronized
. 我們再修改一下程式碼吧:
public class A {
// 1. 因為靜態方法只能訪問靜態變數, 所以要把 instance 設為靜態以供 getInstance() 訪問
// 2. 設為 null
private static A instance = null
private A() {
}
// 加上synchronized 關鍵字解決執行緒安全問題
public synchronized static A getInstance() {
// 第一次呼叫 getInstance() 時, instance 一定為 null, 這時候要進行例項的建立
if (instance == null) {
instance = new A();
}
// instance 除了第一次之外, 都不為null, 可以直接返回.
return instance;
}
}
這樣的單例模式已經滿足大部分需求了. 但是假如有多個執行緒且呼叫 getInstance()
特別頻繁, 那麼加在方法上的 synchronized
關鍵字可能會降低程式的執行效率. 所以執行緒安全的程式碼還可以再優化一下:
public class A {
// 1. 因為靜態方法只能訪問靜態變數, 所以要把 instance 設為靜態以供 getInstance() 訪問
// 2. 設為 null
// 3. 使用 volatile 關鍵字, 保證多個執行緒能正確處理對 instance 的例項化
private volatile* static A instance = null
private A() {
}
public static A getInstance() {
// 第一次呼叫 getInstance() 時, instance 一定為 null, 這時候要進行例項的建立
if (instance == null) {
synchronized (A.class) {
// 還得判斷問題, 防止不在同一個時間片中執行
if (instance == null) {
instance = new A();
}
}
}
// instance 除了第一次之外, 都不為null, 可以直接返回.
return instance;
}
}
這樣的單例模式的實現, 即照顧到了多執行緒, 又照顧到了效率.
要點
- 有一個
private static
修飾的屬性 - 建構函式要被定義為
private
- 有一個
public static
修飾的方法的訪問方法 - 訪問方法中要延時載入
- 多執行緒安全問題要用
synchronized
- 外界適用時要通過訪問方法來後去類的例項
聲音
Q: 通過不同的引數來初始化單例模式是個好主意麼? 比如:
`public static A getInstance(Config config)`
A: 不是, 根據單例的定義, 一個物件只能被初始化一次, 如果getInstance() 能被傳入引數,那麼外界可以用不同引數來獲取不同的例項了. 這就不再是單例了. 用工廠模式可能會是個更好的選擇.
詳情請見: http://stackoverflow.com/questions/1050991/singleton-with-arguments-in-java
Q: 單例真的好麼? A: 篇幅太長, 意見也不統一, 但是有一些觀點還是值得我們借鑑.
http://stackoverflow.com/questions/137975/what-is-so-bad-about-singletons
相關文章
- 單例模式 singleton單例模式
- 單例模式--SingleTon單例模式
- 單例模式(Singleton Pattern)單例模式
- 設計模式—singleton(單例模式)設計模式單例
- 設計模式 - 單例模式(Singleton)設計模式單例
- Singleton 單例設計模式單例設計模式
- Singleton——單例模式(8種)單例模式
- java設計模式-單例模式SingletonJava設計模式單例
- 設計模式之單例模式 - Singleton設計模式單例
- 設計模式——3單例模式(Singleton)設計模式單例
- [DELPHI]單例模式(singleton) 陳省單例模式
- 設計模式--單例(Singleton Pattern)設計模式單例
- Singleton(單例)——物件建立型模式單例物件模式
- PHP設計模式(四)單例模式(Singleton)PHP設計模式單例
- 設計模式之單例模式(Singleton Pattern)設計模式單例
- Java設計模式——單例模式(Singleton pattern)Java設計模式單例
- 《設計模式》 - 1. 單例模式( Singleton )設計模式單例
- Java設計模式之單例模式(Singleton)Java設計模式單例
- Scala 與設計模式(一):Singleton 單例模式設計模式單例
- PHP設計模式(一)—單例模式(Singleton Pattern)PHP設計模式單例
- Java設計模式之單例模式(Singleton Pattern)Java設計模式單例
- JAVA設計模式之 單例模式【Singleton Pattern】Java設計模式單例
- 單例(singleTon)單例
- 設計模式的征途—1.單例(Singleton)模式設計模式單例
- 深入理解 JavaScript 單例模式 (Singleton Pattern)JavaScript單例模式
- 設計模式之“物件效能模式”: Singleton 單例模式(筆記)設計模式物件單例筆記
- 設計模式 - 單例模式Singleton的8種寫法設計模式單例
- python3中的單例模式SingletonPython單例模式
- 2.C#設計模式系列01_單例模式_SingletonC#設計模式單例
- Python單例模式(Singleton)的N種實現Python單例模式
- 一天一個設計模式(二) - 單例模式(Singleton)設計模式單例
- JAVA中實現單例(Singleton)模式的八種方式Java單例模式
- C++用多種方式實現Singleton單例模式C++單例模式
- OOAD之單例模式Singleton的6種寫法單例模式
- 設計模式之Singleton - 單態模式設計模式
- 建立型模式 --- 單件模式(Singleton Pattern)模式
- Java設計模式之從[反恐精英控制檯]分析單例(Singleton)模式Java設計模式單例
- 我所理解的設計模式(C++實現)——單例模式(Singleton Pattern)設計模式C++單例