字節(jié)面試官問:寫一個(gè)你認(rèn)為最好的單例模式?于是我寫了7個(gè)

面試考察點(diǎn)

考察目的:?jiǎn)卫J娇梢钥疾旆浅6嗟幕A(chǔ)知識(shí)脓魏,因此對(duì)于這種問題兰吟,很多面試官都會(huì)問。小伙伴要注意茂翔,在面試過程中混蔼,但凡能夠從多個(gè)維度考察求職者能力的題目,一定不會(huì)被拋棄珊燎,特別是比較泛的問題惭嚣,比如:”請(qǐng)你說說對(duì)xxx的理解“之類。

考察范圍:工作1到5年經(jīng)驗(yàn)悔政,隨著經(jīng)驗(yàn)的提升晚吞,對(duì)于該問題的考察深度越深。

背景知識(shí)

單例模式谋国,是一種軟件設(shè)計(jì)模式槽地,屬于創(chuàng)建型模式的一種。

它的特性是:保證一個(gè)類只有唯一的一個(gè)實(shí)例芦瘾,并提供一個(gè)全局的訪問點(diǎn)捌蚊。

基于這個(gè)特性可以知道,單例模式的好處是近弟,可以避免對(duì)象的頻繁創(chuàng)建對(duì)于內(nèi)存的消耗逢勾,因?yàn)樗拗屏藢?shí)例的創(chuàng)建,總的來說藐吮,它有以下好處:

  • 控制資源的使用溺拱,通過線程同步來控制資源的并發(fā)訪問;

  • 控制實(shí)例產(chǎn)生的數(shù)量,達(dá)到節(jié)約資源的目的谣辞。

  • 作為通信媒介使用迫摔,也就是數(shù)據(jù)共享,它可以在不建立直接關(guān)聯(lián)的條件下泥从,讓多個(gè)不相關(guān)的兩個(gè)線程或者進(jìn)程之間實(shí)現(xiàn)通信句占。

在實(shí)際應(yīng)用中,單例模式使用最多的就是在Spring的IOC容器中躯嫉,對(duì)于Bean的管理纱烘,默認(rèn)都是單例。一個(gè)bean只會(huì)創(chuàng)建一個(gè)對(duì)象祈餐,存在內(nèi)置map中擂啥,之后無論獲取多少次該bean,都返回同一個(gè)對(duì)象帆阳。

下面來了解單例模式的設(shè)計(jì)哺壶。

提示:本文內(nèi)容篇幅比較長(zhǎng),如果不想了解這么多的,文末也給大家準(zhǔn)備了一點(diǎn)面試題截圖山宾,可以直接跳過這段去看看至扰。

面試題我整理了很久,沖刺大廠很有用资锰,如果有需要敢课,可以點(diǎn)這里免費(fèi)領(lǐng)取。

單例模式設(shè)計(jì)

既然要保證一個(gè)類在運(yùn)行期間只有一個(gè)實(shí)例绷杜,那必然不能使用new關(guān)鍵字來進(jìn)行實(shí)例直秆。

所以,第一步一定是私有化該類的構(gòu)造方法接剩,這樣就防止了調(diào)用方自己創(chuàng)建該類的實(shí)例切厘。

接著,由于外部無法實(shí)例化該對(duì)象懊缺,因此必須從內(nèi)部實(shí)例化之后疫稿,提供一個(gè)全局的訪問入口,來獲取該類的全局唯一實(shí)例鹃两,因此我們可以在類的內(nèi)部定義一個(gè)靜態(tài)變量來引用唯一的實(shí)例遗座,作為對(duì)外提供的實(shí)例訪問對(duì)象】“猓基于這些點(diǎn)途蒋,我們可以得到如下設(shè)計(jì)。

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="java" cid="n773" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">public class Singleton {
// 靜態(tài)字段引用唯一實(shí)例:
private static final Singleton INSTANCE = new Singleton();

// private構(gòu)造方法保證外部無法實(shí)例化:
private Singleton() {
}
}</pre>

接著馋记,還需要給外部一個(gè)訪問該對(duì)象實(shí)例INSTANCE的方法号坡,我們可以提供一個(gè)靜態(tài)方法

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="java" cid="n778" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">public class Singleton {
// 靜態(tài)字段引用唯一實(shí)例:
private static final Singleton INSTANCE = new Singleton();

// 通過靜態(tài)方法返回實(shí)例:
public static Singleton getInstance() {
return INSTANCE;
}

// private構(gòu)造方法保證外部無法實(shí)例化:
private Singleton() {
}
}</pre>

這樣就完成了單例模式的設(shè)計(jì),總結(jié)來看梯醒,單例模式分三步驟宽堆。

  • 使用private私有化構(gòu)造方法,確保外部無法實(shí)例化;

  • 通過private static變量持有唯一實(shí)例茸习,保證全局唯一性;

  • 通過public static方法返回此唯一實(shí)例畜隶,使外部調(diào)用方能獲取到實(shí)例。

單例模式的其他實(shí)現(xiàn)

既然單例模式只需要保證程序運(yùn)行期間只會(huì)產(chǎn)生唯一的實(shí)例号胚,那意味著單例模式還有更多的實(shí)現(xiàn)方法籽慢。

  • 懶漢式單例模式

  • 餓漢式單例模式

  • DCL雙重檢查式單例

  • 靜態(tài)內(nèi)部類

  • 枚舉單例

  • 基于容器實(shí)現(xiàn)單例

懶漢式單例模式

懶漢式,表示不提前創(chuàng)建對(duì)象實(shí)例猫胁,而是在需要的時(shí)候再創(chuàng)建箱亿,代碼如下。

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="java" cid="n796" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">public class Singleton {
private static Singleton instance;

private Singleton() {
}

// synchronized方法,多線程情況下保證單例對(duì)象唯一
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}</pre>

其中杜漠,對(duì)getInstance()方法极景,增加了synchronized同步關(guān)鍵字察净,目的是為了避免在多線程環(huán)境下同一時(shí)刻調(diào)用該方法導(dǎo)致出現(xiàn)多實(shí)例問題(線程的并行執(zhí)行特性帶來的線程安全性問題)驾茴。

優(yōu)點(diǎn): 只有在使用時(shí)才會(huì)實(shí)例化單例盼樟,一定程度上節(jié)約了內(nèi)存資源。缺點(diǎn): 第一次加載時(shí)要立即實(shí)例化锈至,反應(yīng)稍慢晨缴。每次調(diào)用getInstance()方法都會(huì)進(jìn)行同步,這樣會(huì)消耗不必要的資源峡捡。這種模式一般不建議使用击碗。

DCL雙重檢查式單例

DCL雙重檢查式單例模式,是基于餓漢式單例模式的性能優(yōu)化版本们拙。

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="java" cid="n802" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">/**

  • DCL實(shí)現(xiàn)單例模式
    */
    public class Singleton {
    private static volatile Singleton instance = null;

private Singleton() {
}

public static Singleton getInstance() {
// 兩層判空稍途,第一層是為了避免不必要的同步
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {// 第二層是為了在null的情況下創(chuàng)建實(shí)例
instance = new Singleton();
}
}

}
return instance;
}
}</pre>

從代碼中可以看到,DCL模式做了兩處改進(jìn):

在getInstance()方法中砚婆,把synchronized同步鎖的加鎖范圍縮小了械拍。

縮小鎖的范圍能夠帶來性能上的提升,不妨思考一下装盯,在原來的懶漢式模式中坷虑,把synchronized關(guān)鍵字加載方法級(jí)別上,意味著不管是多線程環(huán)境還是單線程環(huán)境埂奈,任何一個(gè)調(diào)用者需要獲得這個(gè)對(duì)象實(shí)例時(shí)迄损,都需要獲得鎖。但是加這個(gè)鎖其實(shí)只有在第一次初始化該實(shí)例的時(shí)候起到保護(hù)作用账磺。后續(xù)的訪問芹敌,應(yīng)該直接返回instance實(shí)例對(duì)象就行。所以把synchroinzed加在方法級(jí)別垮抗,在多線程環(huán)境中必然會(huì)帶來性能上的開銷氏捞。

而DCL模式的改造,就是縮小了加鎖的范圍借宵,只需要保護(hù)該實(shí)例對(duì)象instance在第一次初始化即可幌衣,后續(xù)的訪問,都不需要去競(jìng)爭(zhēng)同步鎖壤玫。因此它的設(shè)計(jì)是:

先判斷instance實(shí)例是否為空豁护,如果是,則增加synchronized類級(jí)別鎖欲间,保護(hù)instance對(duì)象的實(shí)例化過程楚里,避免在多線程環(huán)境下出現(xiàn)多實(shí)例問題。

接著再synchronized同步關(guān)鍵字范圍內(nèi)猎贴,再一次判斷instance實(shí)例是否為空班缎,同樣也是為了避免臨界點(diǎn)時(shí)蝴光,上一個(gè)線程剛初始化完成,下一個(gè)線程進(jìn)入到同步代碼塊導(dǎo)致多實(shí)例問題达址。

在成員變量instance上修飾了volatile關(guān)鍵字蔑祟,該關(guān)鍵字是為了保證可見性。

之所以要加這個(gè)關(guān)鍵字沉唠,是為了避免在JVM中指令重排序帶來的可見性問題疆虚,這個(gè)問題主要體現(xiàn)在instance=new Singleton()這段代碼中。我們來看這段代碼的字節(jié)碼

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="java" cid="n807" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"> 17: new #3 // class org/example/cl04/Singleton
20: dup
21: invokespecial #4 // Method "<init>":()V
24: putstatic #2 // Field instance:Lorg/example/cl04/Singleton;
27: aload_0
28: monitorexit
29: goto 37
32: astore_1
33: aload_0</pre>

關(guān)注以下幾個(gè)指令

而invokespecial #4指令满葛,和astore_1指令径簿,是允許重排序的(關(guān)于重排序問題,就不再本篇文章中說明嘀韧,后續(xù)的面試題中會(huì)分析到)篇亭,就是說執(zhí)行順序有可能astore_1先執(zhí)行, invokespecial #1后執(zhí)行锄贷。

重排序?qū)τ趦蓚€(gè)沒有依賴關(guān)系的指令操作译蒂,CPU和內(nèi)存以及JVM,為了優(yōu)化程序執(zhí)行性能肃叶,會(huì)對(duì)執(zhí)行指令進(jìn)行重排序蹂随。也就是說兩個(gè)指令的執(zhí)行順序不一定會(huì)按照程序編寫順序來執(zhí)行。

因?yàn)樵诙焉辖?duì)象開辟地址以后因惭,地址就已經(jīng)定了岳锁,而“將棧里的Singleton instance與堆上的對(duì)象建立起引用關(guān)聯(lián)” 和 “將對(duì)象里的成員變量進(jìn)行賦值操作” 是沒什么邏輯關(guān)系的。

所以cpu可以進(jìn)行亂序執(zhí)行蹦魔,只要程序最終的結(jié)果是一致的就可以激率。

這種情況,在單線程下沒有問題勿决,但是多線程下乒躺,就會(huì)出現(xiàn)錯(cuò)誤。

試想一下低缩,DCL下嘉冒,線程A在將對(duì)象new出來的時(shí),剛執(zhí)行完new #4指令咆繁,緊接著沒有執(zhí)行invokespecial #4指令讳推,而是執(zhí)行了astore_1,也就是說發(fā)生了指令重排序玩般。

此時(shí)線程B進(jìn)入getInstance()银觅,發(fā)現(xiàn)instance并不為空(因?yàn)橐呀?jīng)有了引用指向了對(duì)象,只不過還沒來得及給對(duì)象里的成員變量賦值)坏为,然后線程B便直接return了一個(gè)“半初始化”對(duì)象(對(duì)象還沒徹底創(chuàng)建完)究驴。

所以DCL里镊绪,需要給instance加上volatile關(guān)鍵字,因?yàn)関olatile在JVM層有一個(gè)特性叫內(nèi)存屏障洒忧,可以防止指令重排序蝴韭,從而保證了程序的正確性。

  1. new #3 :這行指令是說在堆上的某個(gè)地址處開辟了一塊空間作為Singleton對(duì)象

  2. invokespecial #4 :這行指令是說將對(duì)象里的成員變量進(jìn)行賦值操作

  3. astore_1 :這行指令是說將棧里的Singleton instance與堆上的對(duì)象建立起引用關(guān)聯(lián)

關(guān)于DCL模式的優(yōu)缺點(diǎn):

優(yōu)點(diǎn):資源利用率高跑慕,既能夠在需要的時(shí)候才初始化實(shí)例万皿,又能保證線程安全摧找,同時(shí)調(diào)用getInstance()方法不進(jìn)行同步鎖核行,效率高。

缺點(diǎn):第一次加載時(shí)稍慢蹬耘,由于Java內(nèi)存模型的原因偶爾會(huì)失敗芝雪。在高并發(fā)環(huán)境下也有一定的缺陷,雖然發(fā)生概率很小综苔。

DCL模式是使用最多的單例模式實(shí)現(xiàn)方式惩系,除非代碼在并發(fā)場(chǎng)景比較復(fù)雜,否則如筛,這種方式基本都能滿足需求堡牡。

餓漢式單例模式

在類加載的時(shí)候不創(chuàng)建單例實(shí)例。只有在第一次請(qǐng)求實(shí)例的時(shí)候的時(shí)候創(chuàng)建杨刨,并且只在第一次創(chuàng)建后晤柄,以后不再創(chuàng)建該類的實(shí)例。

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="java" cid="n821" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">/**

  • 餓漢式實(shí)現(xiàn)單例模式
    */
    public class Singleton {
    private static Singleton instance = new Singleton();

private Singleton() {
}

public static Singleton getInstance() {
return instance;
}
}</pre>

由于static關(guān)鍵字修飾的屬性妖胀,表示這個(gè)成員屬于類本身芥颈,不屬于實(shí)例,運(yùn)行時(shí)赚抡,Java 虛擬機(jī)只為靜態(tài)變量分配一次內(nèi)存爬坑,在類加載的過程中完成靜態(tài)變量的內(nèi)存分配。

所以在類加載的時(shí)候就創(chuàng)建好對(duì)象實(shí)例涂臣,后續(xù)在訪問時(shí)直接獲取該實(shí)例即可盾计。

而該模式的優(yōu)缺點(diǎn)也非常明顯。

優(yōu)點(diǎn):線程安全赁遗,不需要考慮并發(fā)安全性署辉。

缺點(diǎn):浪費(fèi)內(nèi)存空間,不管該對(duì)象是否被使用到吼和,都會(huì)在啟動(dòng)時(shí)提前分配內(nèi)存空間涨薪。

靜態(tài)內(nèi)部類

靜態(tài)內(nèi)部類,是基于餓漢式模式下的優(yōu)化炫乓。

第一次加載Singleton類時(shí)不會(huì)初始化instance刚夺,只有在第一次調(diào)用getInstance()方法時(shí)献丑,虛擬機(jī)會(huì)加載SingletonHolder類,初始化instance侠姑。instance 的唯一性创橄、創(chuàng)建過程的線程安全性,都由 JVM 來保證莽红。

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="java" cid="n827" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">/**

  • 靜態(tài)內(nèi)部類實(shí)現(xiàn)單例模式
    */
    public class Singleton {
    private Singleton() {
    }

public static Singleton getInstance() {
return SingletonHolder.instance;
}

/**

  • 靜態(tài)內(nèi)部類
    */
    private static class SingletonHolder {
    private static Singleton instance = new Singleton();
    }
    }</pre>

這種方式既保證線程安全妥畏,單例對(duì)象的唯一,也延遲了單例的初始化安吁,推薦使用這種方式來實(shí)現(xiàn)單例模式醉蚁。

靜態(tài)內(nèi)部類不會(huì)因?yàn)橥獠款惖募虞d而加載,同時(shí)靜態(tài)內(nèi)部類的加載不需要依附外部類鬼店,在使用時(shí)才加載网棍,不過在加載靜態(tài)內(nèi)部類的過程中也會(huì)加載外部類

知識(shí)點(diǎn):如果用static來修飾一個(gè)內(nèi)部類,那么就是靜態(tài)內(nèi)部類妇智。這個(gè)內(nèi)部類屬于外部類本身滥玷,但是不屬于外部類的任何對(duì)象。因此使用static修飾的內(nèi)部類稱為靜態(tài)內(nèi)部類巍棱。靜態(tài)內(nèi)部類有如下規(guī)則:

  • 靜態(tài)內(nèi)部類不能訪問外部類的實(shí)例成員惑畴,只能訪問外部類的類成員。
  • 外部類可以使用靜態(tài)內(nèi)部類的類名作為調(diào)用者來訪問靜態(tài)內(nèi)部類的類成員航徙,也可以使用靜態(tài)內(nèi)部類對(duì)象訪問其實(shí)例成員如贷。

靜態(tài)內(nèi)部類單例優(yōu)點(diǎn):

  • 對(duì)象的創(chuàng)建是線程安全的。

  • 支持延時(shí)加載捉偏。

  • 獲取對(duì)象時(shí)不需要加鎖倒得。

  • 這是一種比較常用的模式之一。

基于枚舉實(shí)現(xiàn)單例

用枚舉來實(shí)現(xiàn)單例夭禽,是最簡(jiǎn)單的方式霞掺。這種實(shí)現(xiàn)方式通過Java枚舉類型本身的特性,保證了實(shí)例創(chuàng)建的線程安全性和實(shí)例的唯一性讹躯。

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="java" cid="n842" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">public enum SingletonEnum {

INSTANCE;

public void execute(){
System.out.println("begin execute");
}

public static void main(String[] args) {
SingletonEnum.INSTANCE.execute();
}
}</pre>

基于枚舉實(shí)現(xiàn)單例會(huì)發(fā)現(xiàn)它并不需要前面描述的幾個(gè)操作

  • 構(gòu)造方法私有化

  • 實(shí)例化的變量引用私有化

  • 獲取實(shí)例的方法共有

這類的方式實(shí)現(xiàn)枚舉其實(shí)并不保險(xiǎn)菩彬,因?yàn)樗接谢瘶?gòu)造并不能抵御反射攻擊.

這種方式是Effective Java作者Josh Bloch提倡的方式,它不僅能避免多線程同步問題潮梯,而且還能防止反序列化重新創(chuàng)建新的對(duì)象骗灶,可謂是很堅(jiān)強(qiáng)的壁壘啊。

基于容器實(shí)現(xiàn)單例

下面的代碼演示了基于容器的方式來管理單例秉馏。

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="java" cid="n852" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">import java.util.HashMap;
import java.util.Map;
/**

  • 容器類實(shí)現(xiàn)單例模式
    */
    public class SingletonManager {
    private static Map<String, Object> objMap = new HashMap<String, Object>();

public static void regsiterService(String key, Object instance) {
if (!objMap.containsKey(key)) {
objMap.put(key, instance);
}
}

public static Object getService(String key) {
return objMap.get(key);
}
}</pre>

SingletonManager可以管理多個(gè)單例類型耙旦,在程序的初始化時(shí),將多個(gè)單例類型注入到一個(gè)統(tǒng)一管理的類中萝究,使用時(shí)根據(jù)key獲取對(duì)象對(duì)應(yīng)類型的對(duì)象免都。這種方式可以通過統(tǒng)一的接口獲取操作锉罐,隱藏了具體實(shí)現(xiàn),降低了耦合度绕娘。

關(guān)于單例模式的破壞

前面在分析枚舉類實(shí)現(xiàn)單例模式時(shí)脓规,有提到一個(gè)問題,就是私有化構(gòu)造险领,會(huì)被反射破壞侨舆,導(dǎo)致出現(xiàn)多實(shí)例問題。

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="java" cid="n858" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">public class Singleton {

private static volatile Singleton instance = null;

private Singleton() {
}

public static Singleton getInstance() {
// 兩層判空绢陌,第一層是為了避免不必要的同步
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {// 第二層是為了在null的情況下創(chuàng)建實(shí)例
instance = new Singleton();
}
}
}
return instance;
}

public static void main(String[] args) throws Exception{
Singleton instance=Singleton.getInstance();
Constructor<Singleton> constructor=Singleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
Singleton refInstance=constructor.newInstance();
System.out.println(instance);
System.out.println(refInstance);
System.out.println(instance==refInstance);

}
}</pre>

運(yùn)行結(jié)果如下

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="java" cid="n863" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">org.example.cl04.Singleton@29453f44
org.example.cl04.Singleton@5cad8086
false</pre>

由于反射可以破壞private特性挨下,所以凡是通過private私有化構(gòu)造實(shí)現(xiàn)的單例模式,都能夠被反射破壞從而出現(xiàn)多實(shí)例問題下面。

可能有人會(huì)問复颈,我們沒事干嘛要去破壞單例呢?直接基于這個(gè)入口訪問就不會(huì)有問題啊?

理論上來說是這樣,但是沥割,假設(shè)遇到下面這種情況呢?

下面的代碼演示的是通過對(duì)象流實(shí)現(xiàn)Singleton的序列化和反序列化。

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="java" cid="n869" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">public class Singleton implements Serializable {

private static volatile Singleton instance = null;

private Singleton() {
}

public static Singleton getInstance() {
// 兩層判空凿菩,第一層是為了避免不必要的同步
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {// 第二層是為了在null的情況下創(chuàng)建實(shí)例
instance = new Singleton();
}
}
}
return instance;
}

public static void main(String[] args) throws Exception {
Singleton instance=Singleton.getInstance();
ByteArrayOutputStream baos=new ByteArrayOutputStream();
ObjectOutputStream oos=new ObjectOutputStream(baos);
oos.writeObject(instance);
ByteArrayInputStream bais=new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois=new ObjectInputStream(bais);
Singleton ri=(Singleton) ois.readObject();
System.out.println(instance);
System.out.println(ri);
System.out.println(instance==ri);
}
}</pre>

運(yùn)行結(jié)果如下

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="java" cid="n874" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">org.example.cl04.Singleton@36baf30c
org.example.cl04.Singleton@66a29884
false</pre>

可以看到机杜,序列化的方式,也會(huì)破壞單例模式衅谷。

枚舉類單例的破壞測(cè)試

可能有人會(huì)問椒拗,枚舉難道就不能破壞嗎?

我們可以試試看,代碼如下获黔。

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="java" cid="n880" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">public enum SingletonEnum {

INSTANCE;

public void execute(){
System.out.println("begin execute");
}

public static void main(String[] args) throws Exception{
SingletonEnum instance=SingletonEnum.INSTANCE;
Constructor<SingletonEnum> constructor=SingletonEnum.class.getDeclaredConstructor();
constructor.setAccessible(true);
SingletonEnum refInstance=constructor.newInstance();
System.out.println(instance);
System.out.println(refInstance);
System.out.println(instance==refInstance);
}
}</pre>

運(yùn)行結(jié)果如下

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="java" cid="n885" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">Exception in thread "main" java.lang.NoSuchMethodException: org.example.cl04.SingletonEnum.<init>()
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.getDeclaredConstructor(Class.java:2178)
at org.example.cl04.SingletonEnum.main(SingletonEnum.java:15)</pre>

從錯(cuò)誤來看蚀苛,似乎是沒有一個(gè)空的構(gòu)造函數(shù)?這里并沒有證明 反射無法破壞單例。

下面是Enum這類的源碼玷氏,所有枚舉類都繼承了Enum這個(gè)抽象類堵未。

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="java" cid="n890" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">public abstract class Enum<E extends Enum<E>>
implements Comparable<E>, Serializable {
/**

  • The name of this enum constant, as declared in the enum declaration.
  • Most programmers should use the {@link #toString} method rather than
  • accessing this field.
    */
    private final String name;

/**

  • Returns the name of this enum constant, exactly as declared in its
  • enum declaration.
  • <b>Most programmers should use the {@link #toString} method in
  • preference to this one, as the toString method may return
  • a more user-friendly name.</b> This method is designed primarily for
  • use in specialized situations where correctness depends on getting the
  • exact name, which will not vary from release to release.
  • @return the name of this enum constant
    */
    public final String name() {
    return name;
    }

/**

  • The ordinal of this enumeration constant (its position
  • in the enum declaration, where the initial constant is assigned
  • an ordinal of zero).
  • Most programmers will have no use for this field. It is designed
  • for use by sophisticated enum-based data structures, such as
  • {@link java.util.EnumSet} and {@link java.util.EnumMap}.
    */
    private final int ordinal;

/**

  • Returns the ordinal of this enumeration constant (its position
  • in its enum declaration, where the initial constant is assigned
  • an ordinal of zero).
  • Most programmers will have no use for this method. It is
  • designed for use by sophisticated enum-based data structures, such
  • as {@link java.util.EnumSet} and {@link java.util.EnumMap}.
  • @return the ordinal of this enumeration constant
    */
    public final int ordinal() {
    return ordinal;
    }

/**

  • Sole constructor. Programmers cannot invoke this constructor.
  • It is for use by code emitted by the compiler in response to
  • enum type declarations.
  • @param name - The name of this enum constant, which is the identifier
  •           used to declare it.
    
  • @param ordinal - The ordinal of this enumeration constant (its position
  •     in the enum declaration, where the initial constant is assigned
    
  •     an ordinal of zero).
    

*/
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}
}</pre>

該類有一個(gè)唯一的構(gòu)造方法,接受兩個(gè)參數(shù)分別是:name和ordinal

那我們嘗試通過這個(gè)構(gòu)造方法來創(chuàng)建一下實(shí)例盏触,演示代碼如下渗蟹。

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="java" cid="n895" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">public enum SingletonEnum {

INSTANCE;

public void execute(){
System.out.println("begin execute");
}

public static void main(String[] args) throws Exception{
SingletonEnum instance=SingletonEnum.INSTANCE;
Constructor<SingletonEnum> constructor=SingletonEnum.class.getDeclaredConstructor(String.class,int.class);
constructor.setAccessible(true);
SingletonEnum refInstance=constructor.newInstance("refinstance",2);
System.out.println(instance);
System.out.println(refInstance);
System.out.println(instance==refInstance);
}
}</pre>

運(yùn)行上述代碼,執(zhí)行結(jié)果如下

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="java" cid="n900" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
at org.example.cl04.SingletonEnum.main(SingletonEnum.java:17)</pre>

從錯(cuò)誤信息來看赞辩,我們成功獲取到了Constructor這個(gè)構(gòu)造器雌芽,但是在newInstance時(shí)報(bào)錯(cuò)。

定位到出錯(cuò)的源碼位置辨嗽。

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="java" cid="n905" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
ConstructorAccessor ca = constructorAccessor; // read volatile</pre>

從這段代碼:(clazz.getModifiers() & Modifier.ENUM) != 0說明:反射在通過newInstance創(chuàng)建對(duì)象時(shí)世落,會(huì)檢查該類是否ENUM修飾,如果是則拋出異常糟需,反射失敗屉佳,因此枚舉類型對(duì)反射是絕對(duì)安全的来破。

既然反射無法破壞?那序列化呢?我們?cè)賮碓囋?/p>

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="java" cid="n910" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">public enum SingletonEnum {

INSTANCE;

public void execute(){
System.out.println("begin execute");
}
public static void main(String[] args) throws Exception{
SingletonEnum instance=SingletonEnum.INSTANCE;
ByteArrayOutputStream baos=new ByteArrayOutputStream();
ObjectOutputStream oos=new ObjectOutputStream(baos);
oos.writeObject(instance);
ByteArrayInputStream bais=new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois=new ObjectInputStream(bais);
SingletonEnum ri=(SingletonEnum) ois.readObject();
System.out.println(instance);
System.out.println(ri);
System.out.println(instance==ri);
}
}</pre>

運(yùn)行結(jié)果如下.

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="java" cid="n917" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">INSTANCE
INSTANCE
true</pre>

因此,我們可以得出一個(gè)結(jié)論忘古,枚舉類型是所有單例模式中唯一能夠避免反射破壞導(dǎo)致多實(shí)例問題的設(shè)計(jì)模式徘禁。

綜上,可以得出結(jié)論:枚舉是實(shí)現(xiàn)單例模式的最佳實(shí)踐髓堪。畢竟使用它全都是優(yōu)點(diǎn):

  • 反射安全

  • 序列化/反序列化安全

  • 寫法簡(jiǎn)單

問題解答

面試題:寫一個(gè)你認(rèn)為最好的單例模式

對(duì)于這個(gè)問題设预,想必大家都有答案了无宿,枚舉方式實(shí)現(xiàn)單例才是最好的。

當(dāng)然,回答的時(shí)候要從全方面角度去講解襟己。

  • 單例模式的概念

  • 有哪些方式實(shí)現(xiàn)單例

  • 每種單例模式的優(yōu)缺點(diǎn)

  • 最好的單例模式,以及為什么你覺得它是最好的?

問題總結(jié)

單例模式看起來簡(jiǎn)單课梳,但是學(xué)到極致店量,也還是有很多知識(shí)點(diǎn)的。

比如涉及到線程安全問題换薄、靜態(tài)方法和靜態(tài)成員變量的特征玉雾、枚舉、反射等轻要。

多想再回到從前复旬,大家都只用jsp/servlet,沒有這么多亂七八糟的知識(shí)冲泥,我們只想做個(gè)簡(jiǎn)單的程序員驹碍。

我爆肝整理的這份《史上最全Java面試題》,分25個(gè)專題凡恍,包括JavaOOP志秃、多線程&并發(fā)、JVM嚼酝、Spring浮还、Mysql、Dubbo革半、數(shù)據(jù)結(jié)構(gòu)碑定、算法、微服務(wù)等等.....具體的你們拿到文檔看看就知道有多全面了又官,它將是你沖擊互聯(lián)網(wǎng)大廠的利器延刘。

文檔是免費(fèi)分享的,先截幾張圖給大家看看六敬。

有需要的話碘赖,點(diǎn)這里加我免費(fèi)領(lǐng)取。

目錄

JavaOOP面試題

img

Java集合/泛型面試題

img

多線程&并發(fā)面試題

img

Jvm面試題

img

Mysql面試題

img

Redis面試題

img

Spring面試題(Spring、Spring Boot普泡、Spring Cloud)

img

Dubbo 面試題

img

ZooKeeper 面試題

img

數(shù)據(jù)結(jié)構(gòu)面試題

img

Kafka 面試題

img

微服務(wù) 面試題

img

內(nèi)容比較多播掷,就先展示這些,只有充分地準(zhǔn)備好了面試撼班,才可以更有自信地吊打面試官歧匈!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市砰嘁,隨后出現(xiàn)的幾起案子件炉,更是在濱河造成了極大的恐慌,老刑警劉巖矮湘,帶你破解...
    沈念sama閱讀 218,525評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件斟冕,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡缅阳,警方通過查閱死者的電腦和手機(jī)磕蛇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來十办,“玉大人秀撇,你說我怎么就攤上這事¢俣矗” “怎么了捌袜?”我有些...
    開封第一講書人閱讀 164,862評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)炸枣。 經(jīng)常有香客問我,道長(zhǎng)弄唧,這世上最難降的妖魔是什么适肠? 我笑而不...
    開封第一講書人閱讀 58,728評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮候引,結(jié)果婚禮上侯养,老公的妹妹穿的比我還像新娘。我一直安慰自己澄干,他們只是感情好逛揩,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,743評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著麸俘,像睡著了一般辩稽。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上从媚,一...
    開封第一講書人閱讀 51,590評(píng)論 1 305
  • 那天逞泄,我揣著相機(jī)與錄音,去河邊找鬼。 笑死喷众,一個(gè)胖子當(dāng)著我的面吹牛各谚,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播到千,決...
    沈念sama閱讀 40,330評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼昌渤,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了憔四?” 一聲冷哼從身側(cè)響起膀息,我...
    開封第一講書人閱讀 39,244評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎加矛,沒想到半個(gè)月后履婉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,693評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡斟览,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,885評(píng)論 3 336
  • 正文 我和宋清朗相戀三年毁腿,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片苛茂。...
    茶點(diǎn)故事閱讀 40,001評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡已烤,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出妓羊,到底是詐尸還是另有隱情胯究,我是刑警寧澤,帶...
    沈念sama閱讀 35,723評(píng)論 5 346
  • 正文 年R本政府宣布躁绸,位于F島的核電站裕循,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏净刮。R本人自食惡果不足惜剥哑,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,343評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望淹父。 院中可真熱鬧株婴,春花似錦、人聲如沸暑认。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蘸际。三九已至座哩,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間捡鱼,已是汗流浹背八回。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工酷愧, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人缠诅。 一個(gè)月前我還...
    沈念sama閱讀 48,191評(píng)論 3 370
  • 正文 我出身青樓溶浴,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親管引。 傳聞我的和親對(duì)象是個(gè)殘疾皇子士败,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,955評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容