思維導(dǎo)圖
前言
單例模式下缀雳,類負(fù)責(zé)創(chuàng)建自己的對(duì)象羞延,同時(shí)確保只有單個(gè)對(duì)象被創(chuàng)建渣淳。這個(gè)類提供了一種訪問其唯一的對(duì)象的方式,可以直接訪問伴箩,不需要實(shí)例化該類的對(duì)象入愧。
實(shí)現(xiàn)要點(diǎn):
- 構(gòu)造函數(shù)私有
- 該類僅有一個(gè)實(shí)例,并提供一個(gè)訪問它的全局訪問點(diǎn)
懶漢式(非線程安全)
這種方式是最基本的實(shí)現(xiàn)方式嗤谚,這種實(shí)現(xiàn)最大的問題就是不支持多線程棺蛛。因?yàn)闆]有加鎖 synchronized,所以嚴(yán)格意義上它并不算單例模式巩步。
這種方式 lazy loading 很明顯旁赊,不要求線程安全,在多線程不能正常工作渗钉。
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
這個(gè)類可以滿足基本要求彤恶,但是,像這樣毫無線程安全保護(hù)的類鳄橘,如果我們把它放入多線程的環(huán)境下声离,肯定就會(huì)出現(xiàn)問題了,如何解決瘫怜?我們首先會(huì)想到對(duì)getInstance方法加synchronized關(guān)鍵字术徊,如下
懶漢式(線程安全)
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
synchronized關(guān)鍵字鎖住的是這個(gè)對(duì)象,這樣的用法鲸湃,在性能上會(huì)有所下降赠涮,因?yàn)槊看握{(diào)用getInstance(),都要對(duì)對(duì)象上鎖暗挑,事實(shí)上谎柄,只有在第一次創(chuàng)建對(duì)象的時(shí)候需要加鎖她紫,之后就不需要了,所以,這個(gè)地方需要改進(jìn)筷黔。我們改成下面這個(gè):
雙檢鎖
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
似乎解決了之前提到的問題,將synchronized關(guān)鍵字加在了內(nèi)部,也就是說當(dāng)調(diào)用的時(shí)候是不需要加鎖的,只有在instance為null洛史,并創(chuàng)建對(duì)象的時(shí)候才需要加鎖,性能有一定的提升酱吝。但是也殖,這樣的情況,還是有可能有問題的务热,看下面的情況:
在Java指令中創(chuàng)建對(duì)象和賦值操作是分開進(jìn)行的忆嗜,也就是說instance = new Singleton()
語句是分兩步執(zhí)行的。但是JVM并不保證這兩個(gè)操作的先后順序陕习,也就是說有可能JVM會(huì)為新的Singleton實(shí)例分配空間霎褐,然后直接賦值給instance成員,然后再去初始化這個(gè)Singleton實(shí)例该镣,這樣就可能出錯(cuò)了冻璃。
我們對(duì)該程序做進(jìn)一步優(yōu)化:餓漢模式
餓漢式
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
它基于 classloader 機(jī)制避免了多線程的同步問題,不過损合,instance 在類裝載時(shí)就實(shí)例化省艳,雖然導(dǎo)致類裝載的原因有很多種,在單例模式中大多數(shù)都是調(diào)用 getInstance 方法嫁审, 但是也不能確定有其他的方式(或者其他的靜態(tài)方法)導(dǎo)致類裝載跋炕,這時(shí)候初始化 instance 顯然沒有達(dá)到 lazy loading 的效果。
這種方式比較常用律适,但容易產(chǎn)生垃圾對(duì)象辐烂。
優(yōu)點(diǎn):沒有加鎖,執(zhí)行效率會(huì)提高捂贿。
缺點(diǎn):類加載時(shí)就初始化纠修,浪費(fèi)內(nèi)存。
如果實(shí)例化 instance 很消耗資源厂僧,所以想讓它延遲加載扣草,另外一方面,又不希望在 Singleton 類加載時(shí)就實(shí)例化颜屠,因?yàn)椴荒艽_保 Singleton 類還可能在其他的地方被主動(dòng)使用從而被加載辰妙,那么這個(gè)時(shí)候?qū)嵗?instance 顯然是不合適的,因此可做如下改進(jìn):
靜態(tài)內(nèi)部類
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
這種方式能達(dá)到雙檢鎖方式一樣的功效甫窟,但實(shí)現(xiàn)更簡(jiǎn)單密浑。對(duì)靜態(tài)域使用延遲初始化,應(yīng)使用這種方式而不是雙檢鎖方式粗井。這種方式只適用于靜態(tài)域的情況肴掷,雙檢鎖方式可在實(shí)例域需要延遲初始化時(shí)使用敬锐。