面試官真是搞笑!讓實(shí)現(xiàn)線程安全的單例,又不讓使用synchronized!
單例模式,是Java中比較常見的一個(gè)設(shè)計(jì)模式,也是我在面試時(shí)經(jīng)常會(huì)問到的一個(gè)問題。
經(jīng)過我的初步統(tǒng)計(jì),基本上有60%左右的人可以說出2-4種單例的實(shí)現(xiàn)方式,有40%左右的人可以說出5-6種單例的實(shí)現(xiàn)方式,只有20%左右的人能夠說出7種單例的實(shí)現(xiàn)。
而只有不到1%的人能夠說出7種以上的單例實(shí)現(xiàn)。
其實(shí),作為面試官,我大多數(shù)情況下之所以問單例模式,是因?yàn)檫@個(gè)題目可以問到很多知識(shí)點(diǎn)。
比如線程安全、類加載機(jī)制、synchronized的原理、volatile的原理、指令重排與內(nèi)存屏障、枚舉的實(shí)現(xiàn)、反射與單例模式、序列化如何破壞單例、CAS、CAS的ABA問題、Threadlocal等知識(shí)。
一般情況下,只需要從單例開始問起,大概就可以完成一場(chǎng)面試的整個(gè)流程,把我想問的東西都問完,可以比較全面的了解一個(gè)面試者的水平。
以下,是一次面試現(xiàn)場(chǎng)的還原,從單例模式開始:
Q:你知道怎么不使用synchronized和lock實(shí)現(xiàn)一個(gè)線程安全的單例嗎?A:我知道,可以使用"靜態(tài)內(nèi)部類"實(shí)現(xiàn)。
靜態(tài)內(nèi)部類實(shí)現(xiàn)單例模式:
public class Singleton { private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } private Singleton (){} public static final Singleton getInstance() { return SingletonHolder.INSTANCE; } }Q:除了靜態(tài)內(nèi)部類還會(huì)其他的方式嗎?A:還有就是兩種餓漢模式。
餓漢實(shí)現(xiàn)單例模式:
? ??public class Singleton {private static Singleton instance = new Singleton();private Singleton (){}public static Singleton getInstance() {return instance;}}
餓漢變種實(shí)現(xiàn)單例模式:
public class Singleton {private Singleton instance = null;static {instance = new Singleton();}private Singleton (){}public static Singleton getInstance() {return this.instance;}}
Q:那你上面提到的幾種都是線程安全的嗎?A:是線程安全的Q:那是如何做到線程安全的呢?A:應(yīng)該是因?yàn)槲沂褂昧藄tatic,然后類加載的時(shí)候就線程安全了吧?Q:其實(shí)你說的并不完全對(duì),因?yàn)橐陨蠋追N雖然沒有直接使用synchronized,但是也是間接用到了。(這里面根據(jù)回答情況會(huì)朝兩個(gè)不同的方向展開:1、類加載機(jī)制、模塊化等;2、繼續(xù)深入問單例模式)
類加載過程的線程安全性保證
以上的靜態(tài)內(nèi)部類、餓漢等模式均是通過定義靜態(tài)的成員變量,以保證單例對(duì)象可以在類初始化的過程中被實(shí)例化。
這其實(shí)是利用了ClassLoader的線程安全機(jī)制。ClassLoader的loadClass方法在加載類的時(shí)候使用了synchronized關(guān)鍵字。
所以, 除非被重寫,這個(gè)方法默認(rèn)在整個(gè)裝載過程中都是線程安全的。所以在類加載過程中對(duì)象的創(chuàng)建也是線程安全的。
Q:那還回到剛開始的問題,你知道怎么不使用synchronized和lock實(shí)現(xiàn)一個(gè)線程安全的單例嗎?(并不是故意窮追不舍,而是希望能可以引發(fā)面試者的更多思考)A:額、、、那枚舉吧,枚舉也可以實(shí)現(xiàn)單例。
枚舉實(shí)現(xiàn)單例模式:
public enum Singleton {INSTANCE;public void whateverMethod() {}}
Q:那你知道枚舉單例的原理嗎?如何保證線程安全的呢?
枚舉其實(shí)底層是依賴Enum類實(shí)現(xiàn)的,這個(gè)類的成員變量都是static類型的,并且在靜態(tài)代碼塊中實(shí)例化的,和餓漢有點(diǎn)像, 所以他天然是線程安全的。
Q:所以,枚舉其實(shí)也是借助了synchronized的,那你知道哪種方式可以完全不使用synchronized的嗎?A:en....我想想Q:(過了一會(huì)他好像沒有思路)你知道CAS嗎?使用CAS可以實(shí)現(xiàn)單例嗎?(面試中,如果面試者對(duì)于鎖比較了解的話,那我大多數(shù)情況下都會(huì)繼續(xù)朝兩個(gè)方向深入問:1、鎖的實(shí)現(xiàn)原理;2、非鎖,如CAS、ThreadLocal等)A:哦,我知道,CAS是一項(xiàng)樂觀鎖技術(shù),當(dāng)多個(gè)線程嘗試使用CAS同時(shí)更新一個(gè)變量時(shí),只有其中一個(gè)線程能更新成功。
借助CAS(AtomicReference)實(shí)現(xiàn)單例模式:
Q:使用CAS實(shí)現(xiàn)的單例有沒有什么優(yōu)缺點(diǎn)呀?A:用CAS的好處在于不需要使用傳統(tǒng)的鎖機(jī)制來保證線程安全,CAS是一種基于忙等待的算法,依賴底層硬件的實(shí)現(xiàn),相對(duì)于鎖它沒有線程切換和阻塞的額外消耗,可以支持較大的并行度。Q:你說的好像是優(yōu)點(diǎn)?那缺點(diǎn)呢?public class Singleton {private static final AtomicReferenceINSTANCE = new AtomicReference (); private Singleton() {}public static Singleton getInstance() {for (;;) {Singleton singleton = INSTANCE.get();if (null != singleton) {return singleton;}singleton = new Singleton();if (INSTANCE.compareAndSet(null, singleton)) {return singleton;}}}}
CAS實(shí)現(xiàn)的單例的缺點(diǎn)
CAS的一個(gè)重要缺點(diǎn)在于如果忙等待一直執(zhí)行不成功(一直在死循環(huán)中),會(huì)對(duì)CPU造成較大的執(zhí)行開銷。
另外,代碼中,如果N個(gè)線程同時(shí)執(zhí)行到 singleton = new Singleton();的時(shí)候,會(huì)有大量對(duì)象被創(chuàng)建,可能導(dǎo)致內(nèi)存溢出。
Q:好的,除了使用CAS以外,你還知道有什么辦法可以不使用synchronized實(shí)現(xiàn)單例嗎?A:這回真的不太知道了。Q:(那我再提醒他一下吧)可以考慮下ThreadLocal,看看能不能實(shí)現(xiàn)?(面試者沒有思路的時(shí)候,我?guī)缀醵紩?huì)先做一下提醒,實(shí)在沒有思路再換下一個(gè)問題)A:ThreadLocal?這也可以嗎?Q:你先說下你理解的ThreadLocal是什么吧(通過他的回答,貌似對(duì)這個(gè)思路有些疑惑,不著急。先問一個(gè)簡(jiǎn)單的問題,讓面試者放松一下,找找自信,然后再繼續(xù)問)ThreadLoacal
ThreadLocal會(huì)為每一個(gè)線程提供一個(gè)獨(dú)立的變量副本,從而隔離了多個(gè)線程對(duì)數(shù)據(jù)的訪問沖突。對(duì)于多線程資源共享的問題,同步機(jī)制(synchronized)采用了“以時(shí)間換空間”的方式,而ThreadLocal采用了“以空間換時(shí)間”的方式。
同步機(jī)制僅提供一份變量,讓不同的線程排隊(duì)訪問,而ThreadLocal為每一個(gè)線程都提供了一份變量,因此可以同時(shí)訪問而互不影響。
Q:那理論上是不是可以使用ThreadLocal來實(shí)現(xiàn)單例呢?A:應(yīng)該也是可行的。使用ThreadLocal實(shí)現(xiàn)單例模式:
public class Singleton {private static final ThreadLocalsingleton = new ThreadLocal() { @Overrideprotected Singleton initialValue() {return new Singleton();}};public static Singleton getInstance() {return singleton.get();}private Singleton() {}}
Q:嗯嗯,好的,那有關(guān)單例模式的實(shí)現(xiàn)的問題我就問的差不多了。(ThreadLocal這種寫法主要是考察面試者對(duì)于ThreadLocal的理解,以及是否可以把知識(shí)活學(xué)活用,但是實(shí)際上,這種所謂的"單例",其實(shí)失去了單例的意義...)(但是說實(shí)話,能回答到這一題的人很少,大多數(shù)面試者基本上在前面幾道題就已經(jīng)沒有思路了,大多數(shù)情況下根本不會(huì)問到這個(gè)問題就要改方向了)A:(心中竊喜)嗯嗯,學(xué)習(xí)到很多,感謝Q:那...你知道如何破壞單例嗎?(單例問題,必問的一個(gè)。通過這個(gè)引申到序列化和反射的相關(guān)知識(shí))A:(額....)
長按關(guān)注,還原真實(shí)面試現(xiàn)場(chǎng)
