手撕?jiǎn)卫J讲还苁枪P試還是面試元莫,都是高頻題了赖阻。
今天就來(lái)說(shuō)一下單例模式的原理和 6 種實(shí)現(xiàn)方式。
文章目錄:
一柒竞、單例模式的定義
定義: 確保一個(gè)類只有一個(gè)實(shí)例政供,并提供該實(shí)例的全局訪問(wèn)點(diǎn)。
這樣做的好處是:有些實(shí)例朽基,全局只需要一個(gè)就夠了布隔,使用單例模式就可以避免一個(gè)全局使用的類,頻繁的創(chuàng)建與銷毀稼虎,耗費(fèi)系統(tǒng)資源衅檀。
二、單例模式的設(shè)計(jì)要素
- 一個(gè)私有構(gòu)造函數(shù) (確保只能單例類自己創(chuàng)建實(shí)例)
- 一個(gè)私有靜態(tài)變量 (確保只有一個(gè)實(shí)例)
- 一個(gè)公有靜態(tài)函數(shù) (給使用者提供調(diào)用方法)
簡(jiǎn)單來(lái)說(shuō)就是霎俩,單例類的構(gòu)造方法不讓其他人修改和使用哀军;并且單例類自己只創(chuàng)建一個(gè)實(shí)例,這個(gè)實(shí)例打却,其他人也無(wú)法修改和直接使用杉适;然后單例類提供一個(gè)調(diào)用方法,想用這個(gè)實(shí)例柳击,只能調(diào)用猿推。這樣就確保了全局只創(chuàng)建了一次實(shí)例。
三捌肴、單例模式的6種實(shí)現(xiàn)及各實(shí)現(xiàn)的優(yōu)缺點(diǎn)
(一)懶漢式(線程不安全)
實(shí)現(xiàn):
public class Singleton {
private static Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getUniqueInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
說(shuō)明: 先不創(chuàng)建實(shí)例蹬叭,當(dāng)?shù)谝淮伪徽{(diào)用時(shí),再創(chuàng)建實(shí)例状知,所以被稱為懶漢式秽五。
優(yōu)點(diǎn): 延遲了實(shí)例化,如果不需要使用該類饥悴,就不會(huì)被實(shí)例化坦喘,節(jié)約了系統(tǒng)資源。
缺點(diǎn): 線程不安全铺坞,多線程環(huán)境下起宽,如果多個(gè)線程同時(shí)進(jìn)入了 if (uniqueInstance == null) ,若此時(shí)還未實(shí)例化济榨,也就是uniqueInstance == null,那么就會(huì)有多個(gè)線程執(zhí)行 uniqueInstance = new Singleton(); 绿映,就會(huì)實(shí)例化多個(gè)實(shí)例擒滑;
(二)餓漢式(線程安全)
實(shí)現(xiàn):
public class Singleton {
private static Singleton uniqueInstance = new Singleton();
private Singleton() {
}
public static Singleton getUniqueInstance() {
return uniqueInstance;
}
}
說(shuō)明: 先不管需不需要使用這個(gè)實(shí)例腐晾,直接先實(shí)例化好實(shí)例 (餓死鬼一樣,所以稱為餓漢式)丐一,然后當(dāng)需要使用的時(shí)候藻糖,直接調(diào)方法就可以使用了。
優(yōu)點(diǎn): 提前實(shí)例化好了一個(gè)實(shí)例库车,避免了線程不安全問(wèn)題的出現(xiàn)巨柒。
缺點(diǎn): 直接實(shí)例化好了實(shí)例,不再延遲實(shí)例化柠衍;若系統(tǒng)沒(méi)有使用這個(gè)實(shí)例洋满,或者系統(tǒng)運(yùn)行很久之后才需要使用這個(gè)實(shí)例,都會(huì)操作系統(tǒng)的資源浪費(fèi)珍坊。
(三)懶漢式(線程安全)
實(shí)現(xiàn):
public class Singleton {
private static Singleton uniqueInstance;
private static singleton() {
}
private static synchronized Singleton getUinqueInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
說(shuō)明: 實(shí)現(xiàn)和 線程不安全的懶漢式 幾乎一樣牺勾,唯一不同的點(diǎn)是,在get方法上 加了一把 鎖阵漏。如此一來(lái)驻民,多個(gè)線程訪問(wèn),每次只有拿到鎖的的線程能夠進(jìn)入該方法履怯,避免了多線程不安全問(wèn)題的出現(xiàn)回还。
優(yōu)點(diǎn): 延遲實(shí)例化,節(jié)約了資源叹洲,并且是線程安全的柠硕。
缺點(diǎn): 雖然解決了線程安全問(wèn)題,但是性能降低了疹味。因?yàn)榻鼋校词箤?shí)例已經(jīng)實(shí)例化了,既后續(xù)不會(huì)再出現(xiàn)線程安全問(wèn)題了糙捺,但是鎖還在诫咱,每次還是只能拿到鎖的線程進(jìn)入該方法,會(huì)使線程阻塞洪灯,等待時(shí)間過(guò)長(zhǎng)坎缭。
(四)雙重檢查鎖實(shí)現(xiàn)(線程安全)
實(shí)現(xiàn):
public class Singleton {
private volatile static Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getUniqueInstance() {
if (uniqueInstance == null) {
synchronized (Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
說(shuō)明: 雙重檢查數(shù)相當(dāng)于是改進(jìn)了 線程安全的懶漢式。線程安全的懶漢式 的缺點(diǎn)是性能降低了签钩,造成的原因是因?yàn)榧词箤?shí)例已經(jīng)實(shí)例化掏呼,依然每次都會(huì)有鎖。而現(xiàn)在铅檩,我們將鎖的位置變了憎夷,并且多加了一個(gè)檢查。 也就是昧旨,先判斷實(shí)例是否已經(jīng)存在拾给,若已經(jīng)存在了祥得,則不會(huì)執(zhí)行判斷方法內(nèi)的有鎖方法了。 而如果蒋得,還沒(méi)有實(shí)例化的時(shí)候级及,多個(gè)線程進(jìn)去了,也沒(méi)有事额衙,因?yàn)槔锩娴姆椒ㄓ墟i饮焦,只會(huì)讓一個(gè)線程進(jìn)入最內(nèi)層方法并實(shí)例化實(shí)例。如此一來(lái)窍侧,最多最多县踢,也就是第一次實(shí)例化的時(shí)候,會(huì)有線程阻塞的情況疏之,后續(xù)便不會(huì)再有線程阻塞的問(wèn)題殿雪。
為什么使用 volatile 關(guān)鍵字修飾了 uniqueInstance 實(shí)例變量 ?
uniqueInstance = new Singleton(); 這段代碼執(zhí)行時(shí)分為三步:
- 為 uniqueInstance 分配內(nèi)存空間
- 初始化 uniqueInstance
- 將 uniqueInstance 指向分配的內(nèi)存地址
正常的執(zhí)行順序當(dāng)然是 1>2>3 锋爪,但是由于 JVM 具有指令重排的特性丙曙,執(zhí)行順序有可能變成 1>3>2。
單線程環(huán)境時(shí)其骄,指令重排并沒(méi)有什么問(wèn)題亏镰;多線程環(huán)境時(shí),會(huì)導(dǎo)致有些線程可能會(huì)獲取到還沒(méi)初始化的實(shí)例拯爽。
例如:線程A 只執(zhí)行了 1 和 3 索抓,此時(shí)線程B來(lái)調(diào)用 getUniqueInstance(),發(fā)現(xiàn) uniqueInstance 不為空毯炮,便獲取 uniqueInstance 實(shí)例逼肯,但是其實(shí)此時(shí)的 uniqueInstance 還沒(méi)有初始化。
解決辦法就是加一個(gè) volatile 關(guān)鍵字修飾 uniqueInstance 桃煎,volatile 會(huì)禁止 JVM 的指令重排篮幢,就可以保證多線程環(huán)境下的安全運(yùn)行。
優(yōu)點(diǎn): 延遲實(shí)例化为迈,節(jié)約了資源三椿;線程安全;并且相對(duì)于 線程安全的懶漢式葫辐,性能提高了搜锰。
缺點(diǎn): volatile 關(guān)鍵字,對(duì)性能也有一些影響耿战。
(五)靜態(tài)內(nèi)部類實(shí)現(xiàn)(線程安全)
實(shí)現(xiàn):
public class Singleton {
private Singleton() {
}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getUniqueInstance() {
return SingletonHolder.INSTANCE;
}
}
說(shuō)明: 首先蛋叼,當(dāng)外部類 Singleton 被加載時(shí),靜態(tài)內(nèi)部類 SingletonHolder 并沒(méi)有被加載進(jìn)內(nèi)存剂陡。當(dāng)調(diào)用 getUniqueInstance() 方法時(shí)鸦列,會(huì)運(yùn)行 return SingletonHolder.INSTANCE; 租冠,觸發(fā)了 SingletonHolder.INSTANCE 鹏倘,此時(shí)靜態(tài)內(nèi)部類 SingletonHolder 才會(huì)被加載進(jìn)內(nèi)存薯嗤,并且初始化 INSTANCE 實(shí)例,而且 JVM 會(huì)確保 INSTANCE 只被實(shí)例化一次纤泵。
優(yōu)點(diǎn): 延遲實(shí)例化骆姐,節(jié)約了資源;且線程安全捏题;性能也提高了玻褪。
(六)枚舉類實(shí)現(xiàn)(線程安全)
實(shí)現(xiàn):
public enum Singleton {
INSTANCE;
//添加自己需要的操作
public void doSomeThing() {
}
}
說(shuō)明: 默認(rèn)枚舉實(shí)例的創(chuàng)建就是線程安全的,且在任何情況下都是單例公荧。
優(yōu)點(diǎn): 寫(xiě)法簡(jiǎn)單带射,線程安全,天然防止反射和反序列化調(diào)用循狰。
-
防止反序列化
序列化:把java對(duì)象轉(zhuǎn)換為字節(jié)序列的過(guò)程窟社;
反序列化: 通過(guò)這些字節(jié)序列在內(nèi)存中新建java對(duì)象的過(guò)程;
說(shuō)明: 反序列化 將一個(gè)單例實(shí)例對(duì)象寫(xiě)到磁盤(pán)再讀回來(lái)绪钥,從而獲得了一個(gè)新的實(shí)例灿里。
我們要防止反序列化,避免得到多個(gè)實(shí)例程腹。
枚舉類天然防止反序列化匣吊。
其他單例模式 可以通過(guò) 重寫(xiě) readResolve() 方法,從而防止反序列化寸潦,使實(shí)例唯一重寫(xiě) readResolve() :
private Object readResolve() throws ObjectStreamException{
return singleton;
}
四色鸳、單例模式的應(yīng)用場(chǎng)景
應(yīng)用場(chǎng)景舉例:
- 網(wǎng)站計(jì)數(shù)器。
- 應(yīng)用程序的日志應(yīng)用见转。
- Web項(xiàng)目中的配置對(duì)象的讀取命雀。
- 數(shù)據(jù)庫(kù)連接池。
- 多線程池池户。
- ......
使用場(chǎng)景總結(jié):
- 頻繁實(shí)例化然后又銷毀的對(duì)象咏雌,使用單例模式可以提高性能。
- 經(jīng)常使用的對(duì)象校焦,但實(shí)例化時(shí)耗費(fèi)時(shí)間或者資源多赊抖,如數(shù)據(jù)庫(kù)連接池,使用單例模式寨典,可以提高性能氛雪,降低資源損壞。
- 使用線程池之類的控制資源時(shí)耸成,使用單例模式报亩,可以方便資源之間的通信浴鸿。
如果文章有任何不對(duì)的地方,歡迎大家指正和交流弦追。
整理分享不易岳链,你的點(diǎn)贊、收藏劲件、關(guān)注掸哑,是對(duì)我最大的支持~
關(guān)注猿兄,獲取更多文章~