Singleton單例模式簡介
在應(yīng)用單例模式時,單例對象的類必須保證只有一個實(shí)例存在.許多時候整個系統(tǒng)只需要一個全局對象, 這樣有利于協(xié)調(diào)系統(tǒng)整體行為,如在一個應(yīng)用中,應(yīng)該只有ImageLoader實(shí)例,這個ImageLoader中又含有線程池,緩存系統(tǒng),網(wǎng)絡(luò)請求等, 很消耗資源,所以不能多次構(gòu)建實(shí)例.
構(gòu)建單例模式要保證幾點(diǎn):
- 構(gòu)造方法不對外開放,一般為private
- 通過一個靜態(tài)方法或者枚舉返回單例類對象
- 確保單例對象有且只有一個,尤其在多線程的環(huán)境下.
- 確保單例對象在反序列化時不會重新構(gòu)建對象.
構(gòu)建單例模式的方式:
- 懶漢式: 指全局的單例實(shí)例在第一次被使用時構(gòu)建
- 餓漢式: 指全局的單例實(shí)例在類轉(zhuǎn)載時構(gòu)建.
懶漢式單例
單線程的懶漢式單例:
public class Singleton {
private static Singleton sInstance;
private Singleton() {
}
public static Singleton getInstance() {
if (sInstance == null) {
sInstance = new Singleton();
}
return sInstance;
}
}
這種最簡單的懶漢式單例只有在單線程中才有作用, 如果在多線程中由于多線程執(zhí)行的問題的會因?yàn)榫€程并發(fā)的問題產(chǎn)生多個實(shí)例.所以我們需要同步即:
public class Singleton {
private static Singleton sInstance;
private Singleton() {
}
public static Singleton getInstance() {
if (sInstance == null) {
synchronized (Singleton.class) {
sInstance = new Singleton();
}
}
return sInstance;
}
}
但這種線程同步還是不能做到線程安全的問題, 還是會產(chǎn)生多個實(shí)例對象, 所以我們再一次進(jìn)行判斷nul:
public class Singleton {
private static Singleton sInstance;
private Singleton() {
}
public static Singleton getInstance() {
if (sInstance == null) {
synchronized (Singleton.class) {
if (sInstance == null) {
sInstance = new Singleton();
}
}
}
return sInstance;
}
}
這種雙重判斷DCL單例在JDK小于1.5時還是會為因?yàn)闃?gòu)造對象出現(xiàn)指令重排序的問題, 故而給單例對象添加volatile關(guān)鍵字修飾, 禁止指令重排序; 所以最終版為:
public class Singleton {
private static volatile Singleton sInstance;
private Singleton() {
}
public static Singleton getInstance() {
if (sInstance == null) {
synchronized (Singleton.class) {
if (sInstance == null) {
sInstance = new Singleton();
}
}
}
return sInstance;
}
}
從上面代碼中可以看到:
- 構(gòu)造方法私有化;
- 單例對象使用volatile修飾;
- 使用靜態(tài)方法getInstance()返回單例對象;
- 在構(gòu)建單例對象使用Double-CheckLock(DCL);
使用DLC雙重檢查,第一次判斷sInstance=null,是為了避免不必要的同步問題,第二次判斷sInstance=null是為了避免多次創(chuàng)建實(shí)例對象.其實(shí)在構(gòu)建實(shí)例sInstance=new Singleton()時, 這句代碼并不是一個原子操作,可以分為三步:
- 給Singleton實(shí)例分配內(nèi)存
- 調(diào)用Singleton()構(gòu)造函數(shù),初始化成員變量字段
- 將sInstance對象指向分配的內(nèi)存空間.
但是由于指令重排序的原因,可能不保證上述三點(diǎn)不按照順序執(zhí)行,可能是1-2-3,也可能是2-1-3或者1-3-2,如果是第三點(diǎn)先執(zhí)行,第二點(diǎn)還未執(zhí)行,就切換到其他線程,sInstance已經(jīng)非空,就不會構(gòu)建實(shí)例,使用時就會崩潰.
知道是指令重排序造成的問題后,只要禁止指令重排序既可, 就可以使用volatile關(guān)鍵字修飾sInstance,保證單例對象內(nèi)存可見性.
餓漢式單例
public class Singleton{
private static final Singleton sInstance= new Singleton();
private Singleton(){};
public static Singleton getInstance(){
return sInstance;
}
}
餓漢式單例存在的特點(diǎn)也很明顯: 由于sInstance實(shí)例在類加載時進(jìn)行的,而類的加載是由ClassLoader來進(jìn)行,故而實(shí)例的初始化時機(jī)比較難把握,可能由于初始化時機(jī)太早,造成資源浪費(fèi),如果初始化本身依賴一些其他數(shù)據(jù),那么很難保證其他數(shù)據(jù)在這之前已經(jīng)準(zhǔn)備就緒.
那么什么時候會類加載? 不太嚴(yán)格的說,類的加載一般會出現(xiàn)在一下幾個時機(jī):
- new一個對象是;
- 使用反射創(chuàng)建實(shí)例時;
- 子類被加載時,如果父類還未加載,就先加載父類
- JVM啟動執(zhí)行的主類會首先被加載.
靜態(tài)內(nèi)部類單例模式
DCL雙重檢查單例模式雖然在一定程度上解決了資源消耗,多余的同步,線程安全問題,但是他還是會出現(xiàn)失效問題, 這種問題被稱為雙重檢查鎖定(DCL)失效,建議使用如下代碼:
public class Singleton {
private Singleton(){};
public static Singleton getInstance(){
return SingletonHolder.sInstance;
}
private static class SingletonHolder{
private static final Singleton sInstance= new Singleton();
}
}
當(dāng)?shù)谝淮渭虞dSingleton類并不會初始化sInstance,只有在第一次調(diào)用Singleton的getInstance()才會初始化sInstance.
枚舉單例
public enum SingletonEnum{
INSTANCE;
public void doSomething(){
//...
}
}
枚舉單例的最大優(yōu)勢在于,無償提供了序列化機(jī)制,絕對防止對象實(shí)例化,即使在面對復(fù)雜的序列化或者反射攻擊的時候.