面試考察點(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)存屏障洒忧,可以防止指令重排序蝴韭,從而保證了程序的正確性。
new #3 :這行指令是說在堆上的某個(gè)地址處開辟了一塊空間作為Singleton對(duì)象
invokespecial #4 :這行指令是說將對(duì)象里的成員變量進(jìn)行賦值操作
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)分享的,先截幾張圖給大家看看六敬。
目錄
JavaOOP面試題
Java集合/泛型面試題
多線程&并發(fā)面試題
Jvm面試題
Mysql面試題
Redis面試題
Spring面試題(Spring、Spring Boot普泡、Spring Cloud)
Dubbo 面試題
ZooKeeper 面試題
數(shù)據(jù)結(jié)構(gòu)面試題
Kafka 面試題
微服務(wù) 面試題
內(nèi)容比較多播掷,就先展示這些,只有充分地準(zhǔn)備好了面試撼班,才可以更有自信地吊打面試官歧匈!