一、定義
單例(Singleton)模式的定義:指一個(gè)類只有一個(gè)實(shí)例,且該類能自行創(chuàng)建這個(gè)實(shí)例的一種模式。
二、實(shí)現(xiàn)原理
使用一個(gè)私有構(gòu)造函數(shù)、一個(gè)私有靜態(tài)變量以及一個(gè)公有靜態(tài)函數(shù)來實(shí)現(xiàn)。
私有構(gòu)造函數(shù)保證了不能通過構(gòu)造函數(shù)來創(chuàng)建對象實(shí)例,只能通過公有靜態(tài)函數(shù)返回唯一的私有靜態(tài)變量。
三、實(shí)現(xiàn)方式
1、餓漢式
2、懶漢式
3、靜態(tài)內(nèi)部類實(shí)現(xiàn)
4、枚舉實(shí)現(xiàn)
四、餓漢式
直接實(shí)例化對象,優(yōu)點(diǎn):線程安全,能簡單實(shí)現(xiàn)單例。缺點(diǎn):JVM在加載這個(gè)類時(shí)馬上創(chuàng)建唯一單例實(shí)例,未使用則會(huì)浪費(fèi)資源。同時(shí)加大創(chuàng)建運(yùn)行負(fù)擔(dān)。
public class Singleton {
private static Singleton singleton = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return singleton;
}
}
五、懶漢式(雙重加鎖)
public class Singleton {
private volatile static Singleton singleton;//volatile關(guān)鍵字禁止JVM指令重排
private Singleton() {}
public static Singleton getSingleton() {
if (singleton == null) {//第一次校驗(yàn),避免實(shí)例化對象后仍然加鎖
synchronized (Singleton.class) {
if (singleton == null) {//第二次校驗(yàn),避免多個(gè)線程實(shí)例化對象
singleton = new Singleton();
}
}
}
return singleton;
}
}
1、雙重加鎖的意義
加鎖是為了防止多個(gè)線程同時(shí)通過為空判斷,造成多次創(chuàng)建對象。第一次校驗(yàn)為了避免實(shí)例化對象以后,每一次都會(huì)走加鎖操作。第二次校驗(yàn)是防止多個(gè)線程都會(huì)進(jìn)入第一個(gè) if 語句塊內(nèi)。雖然在 if 語句塊內(nèi)有加鎖操作,但是兩個(gè)線程都會(huì)執(zhí)行 singleton =new Singleton(); 語句,只是按順序執(zhí)行,就會(huì)造成多次實(shí)例化。
2、volatile 關(guān)鍵字的意義
volatile 關(guān)鍵字主要用于禁止JVM指令重排。
singleton =new Singleton(); 創(chuàng)建對象會(huì)經(jīng)過三個(gè)步驟:
(1)分配內(nèi)存空間
(2)初始化對象
(3)將對象指向分配的內(nèi)存地址
但是JVM具有指令重排特性,有可能先執(zhí)行3再執(zhí)行2,在多線程的情況下有可能出現(xiàn)thread1執(zhí)行力3而沒有執(zhí)行2,同時(shí)thread2也進(jìn)入了,判斷singleton不為空,就將其返回,但是事實(shí)上對象還未被成功初始化。使用volatile關(guān)鍵字禁止JVM指令重排解決該問題。
六、靜態(tài)內(nèi)部類實(shí)現(xiàn)
public class Singleton {
private Singleton() {}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
當(dāng) Singleton 類被加載時(shí),靜態(tài)內(nèi)部類 SingletonHolder 沒有被加載進(jìn)內(nèi)存。只有當(dāng)調(diào)用 getInstance()方法從而觸發(fā) SingletonHolder.INSTANCE 時(shí) SingletonHolder 才會(huì)被加載,此時(shí)初始化 INSTANCE 實(shí)例,并且 JVM 能確保 INSTANCE 只被實(shí)例化一次。
這種方式不僅具有延遲初始化的好處,而且由 JVM 提供了對線程安全的支持。
七、枚舉實(shí)現(xiàn)
public enum Singleton {
INSTANCE;
private String name;
public void doSomething() {
//...
}
}
默認(rèn)枚舉實(shí)例的創(chuàng)建是線程安全的,并且在任何情況下都是單例,上述講的幾種單例模式實(shí)現(xiàn)中,有一種情況下他們會(huì)重新創(chuàng)建對象,那就是反序列化,將一個(gè)單例實(shí)例對象寫到磁盤再讀回來,從而獲得了一個(gè)實(shí)例。反序列化操作提供了readResolve方法,這個(gè)方法可以讓開發(fā)人員控制對象的反序列化。在上述的幾個(gè)方法示例中如果要杜絕單例對象被反序列化是重新生成對象,就必須加入如下方法:
private Object readResolve()throws ObjectStreamException {
return singleton;
}
枚舉單例的優(yōu)點(diǎn)就是簡單,但是大部分應(yīng)用開發(fā)很少用枚舉,可讀性并不是很高。