說明:本文為《設(shè)計(jì)模式之禪》的閱讀筆記,主要總結(jié)精華和記錄自己的部分理解。文中代碼部分主要由Kotlin實(shí)現(xiàn)。
1. 定義
Ensure a class has only one instance, and provide a global point of access to it.
確保某一個(gè)類只有一個(gè)實(shí)例,而且自行實(shí)例化并向整個(gè)系統(tǒng)提供這個(gè)實(shí)例。

代碼清單1 單例模式
// Kotlin中實(shí)現(xiàn)單例模式就比較簡單,只需要定義一個(gè)object對(duì)象表達(dá)式即可,無需手動(dòng)設(shè)置構(gòu)造器私有化和提供全局的訪問點(diǎn)
object Singleton {
fun doSomething() {
println("do some thing")
}
private fun readResult(): Any {
return Singleton
}
}
// 在Kotlin中使用Singleton
fun main(args: Array<String>) {
println("Hello World!")
Singleton.doSomething() // 像調(diào)用靜態(tài)方法一樣,調(diào)用單例類中的方法
}
代碼清單2 Singleton 反編譯成 Java 代碼
public final class Singleton {
@NotNull
public static final Singleton INSTANCE;
public final void doSomething() {
String var1 = "do some thing";
System.out.println(var1);
}
private final Object readResult() {
return INSTANCE; // 可以看到readResult方法直接返回了INSTANCE而不是創(chuàng)建新的實(shí)例
}
private Singleton() {
}
static {
// 靜態(tài)代碼塊初始化Singleton實(shí)例
// 只要Singleton被加載了,靜態(tài)代碼塊就會(huì)被調(diào)用,Singleton實(shí)例就會(huì)被創(chuàng)建,并賦值給INSTANCE
Singleton var0 = new Singleton();
INSTANCE = var0;
}
}
2. 優(yōu)點(diǎn)
由于單例模式在內(nèi)存中只有一個(gè)實(shí)例,減少了內(nèi)存開支
特別是一個(gè)對(duì)象需要頻繁地創(chuàng)建、銷毀時(shí),而且創(chuàng)建或銷毀時(shí)性能又無法優(yōu)化,單例模式的優(yōu)勢就非常明顯。由于單例模式只生成一個(gè)實(shí)例,所以減少了系統(tǒng)的性能開銷
當(dāng)一個(gè)對(duì)象的產(chǎn)生需要比較多的資源時(shí),如讀取配置、產(chǎn)生其他依賴對(duì)象時(shí),則可以通過在應(yīng)用啟動(dòng)時(shí)直接產(chǎn)生一個(gè)單例對(duì)象,然后用永久駐留內(nèi)存的方式來解決(在Java EE中采用單例模式時(shí)需要注意JVM垃圾回收機(jī)制)。單例模式可以避免對(duì)資源的多重占用
例如一個(gè)寫文件動(dòng)作,由于只有一個(gè)實(shí)例存在內(nèi)存中,避免對(duì)同一個(gè)資源文件的同時(shí)寫操作。單例模式可以在系統(tǒng)設(shè)置全局的訪問點(diǎn),優(yōu)化和共享資源訪問
例如可以設(shè)計(jì)一個(gè)單例類,負(fù)責(zé)所有數(shù)據(jù)表的映射處理。
3. 缺點(diǎn)
單例模式一般沒有接口,擴(kuò)展很困難,若要擴(kuò)展,除了修改代碼基本上沒有第二種途徑可以實(shí)現(xiàn)。
單例模式為什么不能增加接口呢?因?yàn)榻涌趯?duì)單例模式是沒有任何意義的,它要求“自行實(shí)例化”,并且提供單一實(shí)例,接口或抽象類是不可能被實(shí)例化的。當(dāng)然,在特殊情況下,單例模式可以實(shí)現(xiàn)接口、被繼承等,需要在系統(tǒng)開發(fā)中根據(jù)環(huán)境判斷。單例模式對(duì)測試不利。
在并行開發(fā)環(huán)境中,如果單例模式?jīng)]有完成,是不能進(jìn)行測試的,沒有接口也不能使用mock的方式虛擬一個(gè)對(duì)象。單例模式與單一職責(zé)原則有沖突。
一個(gè)類應(yīng)該只實(shí)現(xiàn)一個(gè)邏輯,而不關(guān)心它是否是單例的,是不是要單例取決于環(huán)境,單例模式把“要單例”和業(yè)務(wù)邏輯融合在一個(gè)類中。
4. 單例模式的使用場景
- 要求生成唯一序列號(hào)的環(huán)境
- 在整個(gè)項(xiàng)目中需要一個(gè)共享訪問點(diǎn)或共享數(shù)據(jù)
例如一個(gè)Web頁面上的計(jì)數(shù)器,可以不用把每次刷新都記錄到數(shù)據(jù)庫中,使用單例模式保持計(jì)數(shù)器的值,并確保是線程安全的。 - 創(chuàng)建一個(gè)對(duì)象需要消耗的資源過多
如要訪問IO和數(shù)據(jù)庫等資源。 - 需要定義大量的靜態(tài)常量和靜態(tài)方法(如工具類)的環(huán)境,可以采用單例模式(當(dāng)然,也可以直接聲明為static的方式)。
5. 注意事項(xiàng)
- 在高并發(fā)情況下,請(qǐng)注意單例模式的線程同步問題
單例模式有幾種不同的實(shí)現(xiàn)方式,有的不會(huì)出現(xiàn)產(chǎn)生多個(gè)實(shí)例的情況,但是有的實(shí)現(xiàn)方式就需要考慮線程同步。 (懶漢式單例模式可能存在多個(gè)線程同時(shí)調(diào)用單例模式中的方法創(chuàng)建實(shí)例,就會(huì)產(chǎn)生兩個(gè)實(shí)例。)
代碼清單3 線程安全的懶漢式單例
class LazySingleton private constructor() {
companion object {
private var lazyInstance: LazySingleton? = null
get() {
return field ?: LazySingleton()
}
@JvmStatic
@Synchronized // 添加synchronized同步鎖
fun getInstance(): LazySingleton {
return requireNotNull(lazyInstance)
}
}
fun doSomething() {
println("do some thing")
}
}
//在Kotlin中調(diào)用
fun main(args: Array<String>) {
LazySingleton.getInstance()
}
- 需要考慮對(duì)象的復(fù)制情況
在Java中,對(duì)象默認(rèn)是不可以被復(fù)制的,若實(shí)現(xiàn)了Cloneable接口,并實(shí)現(xiàn)了clone方法,則可以直接通過對(duì)象復(fù)制方式創(chuàng)建一個(gè)新對(duì)象,對(duì)象復(fù)制是不用調(diào)用類的構(gòu)造函數(shù),因此即使是私有的構(gòu)造函數(shù),對(duì)象仍然可以被復(fù)制。在一般情況下,類復(fù)制的情況不需要考慮,很少會(huì)出現(xiàn)一個(gè)單例類會(huì)主動(dòng)要求被復(fù)制的情況,解決該問題的最好方法就是單例類不要實(shí)現(xiàn)Cloneable接口。
6. 單例模式的擴(kuò)展
- 有上線的多例模式
需要產(chǎn)生固定數(shù)量對(duì)象的模式
采用有上限的多例模式,我們可以在設(shè)計(jì)時(shí)決定在內(nèi)存中有多少個(gè)實(shí)例,方便系統(tǒng)進(jìn)行擴(kuò)展,修正單例可能存在的性能問題,提供系統(tǒng)的響應(yīng)速度。例如讀取文件,我們可以在系統(tǒng)啟動(dòng)時(shí)完成初始化工作,在內(nèi)存中啟動(dòng)固定數(shù)量的reader實(shí)例,然后在需要讀取文件時(shí)就可以快速響應(yīng)。
附1:思維導(dǎo)圖

附2:代碼實(shí)現(xiàn) https://github.com/ooxiaoyan/singlepattern.git