文章來源于公眾號程序員面試現(xiàn)場 鳖宾,作者面試現(xiàn)場
單例模式吼砂,是Java中比較常見的一個設(shè)計模式,也是我在面試時經(jīng)常會問到的一個問題鼎文。
經(jīng)過我的初步統(tǒng)計渔肩,基本上有60%左右的人可以說出2-4種單例的實現(xiàn)方式,有40%左右的人可以說出5-6種單例的實現(xiàn)方式漂问,只有20%左右的人能夠說出7種單例的實現(xiàn)赖瞒。
而只有不到1%的人能夠說出7種以上的單例實現(xiàn)。
其實蚤假,作為面試官栏饮,我大多數(shù)情況下之所以問單例模式,是因為這個題目可以問到很多知識點磷仰。
比如線程安全袍嬉、類加載機制、synchronized的原理灶平、volatile的原理伺通、指令重排與內(nèi)存屏障、枚舉的實現(xiàn)逢享、反射與單例模式罐监、序列化如何破壞單例、CAS瞒爬、CAS的ABA問題弓柱、Threadlocal等知識。
一般情況下侧但,只需要從單例開始問起矢空,大概就可以完成一場面試的整個流程,把我想問的東西都問完禀横,可以比較全面的了解一個面試者的水平屁药。
以下,是一次面試現(xiàn)場的還原柏锄,從單例模式開始:
Q:你知道怎么不使用synchronized和lock實現(xiàn)一個線程安全的單例嗎酿箭?
A:我知道,可以使用"靜態(tài)內(nèi)部類"實現(xiàn)趾娃。
靜態(tài)內(nèi)部類實現(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)部類還會其他的方式嗎缭嫡?
A:還有就是兩種餓漢模式。
餓漢實現(xiàn)單例模式:
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
餓漢變種實現(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)該是因為我使用了static,然后類加載的時候就線程安全了吧?
Q:其實你說的并不完全對讥耗,因為以上幾種雖然沒有直接使用synchronized有勾,但是也是間接用到了。
(這里面根據(jù)回答情況會朝兩個不同的方向展開:1古程、類加載機制蔼卡、模塊化等;2挣磨、繼續(xù)深入問單例模式)
類加載過程的線程安全性保證
以上的靜態(tài)內(nèi)部類雇逞、餓漢等模式均是通過定義靜態(tài)的成員變量,以保證單例對象可以在類初始化的過程中被實例化茁裙。
這其實是利用了ClassLoader的線程安全機制塘砸。ClassLoader的loadClass方法在加載類的時候使用了synchronized關(guān)鍵字。
所以晤锥, 除非被重寫掉蔬,這個方法默認在整個裝載過程中都是線程安全的。所以在類加載過程中對象的創(chuàng)建也是線程安全的矾瘾。
Q:那還回到剛開始的問題女轿,你知道怎么不使用synchronized和lock實現(xiàn)一個線程安全的單例嗎?
(并不是故意窮追不舍壕翩,而是希望能可以引發(fā)面試者的更多思考)
A:額蛉迹、、放妈、那枚舉吧北救,枚舉也可以實現(xiàn)單例。
枚舉實現(xiàn)單例模式:
public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
}
Q:那你知道枚舉單例的原理嗎大猛?如何保證線程安全的呢扭倾?
枚舉單例的線程安全問題
枚舉其實底層是依賴Enum類實現(xiàn)的淀零,這個類的成員變量都是static類型的挽绩,并且在靜態(tài)代碼塊中實例化的,和餓漢有點像驾中, 所以他天然是線程安全的唉堪。
Q:所以,枚舉其實也是借助了synchronized的肩民,那你知道哪種方式可以完全不使用synchronized的嗎唠亚?
A:en....我想想
Q:(過了一會他好像沒有思路)你知道CAS嗎?使用CAS可以實現(xiàn)單例嗎持痰?
(面試中灶搜,如果面試者對于鎖比較了解的話,那我大多數(shù)情況下都會繼續(xù)朝兩個方向深入問:1、鎖的實現(xiàn)原理割卖;2前酿、非鎖,如CAS鹏溯、ThreadLocal等)
A:哦罢维,我知道,CAS是一項樂觀鎖技術(shù)丙挽,當多個線程嘗試使用CAS同時更新一個變量時肺孵,只有其中一個線程能更新成功。
借助CAS(AtomicReference)實現(xiàn)單例模式:
public class Singleton {
private static final AtomicReference<Singleton> INSTANCE = new AtomicReference<Singleton>();
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;
}
}
}
}
Q:使用CAS實現(xiàn)的單例有沒有什么優(yōu)缺點呀颜阐?
A:用CAS的好處在于不需要使用傳統(tǒng)的鎖機制來保證線程安全平窘,CAS是一種基于忙等待的算法,依賴底層硬件的實現(xiàn)凳怨,相對于鎖它沒有線程切換和阻塞的額外消耗初婆,可以支持較大的并行度。
Q:你說的好像是優(yōu)點猿棉?那缺點呢磅叛?
CAS實現(xiàn)的單例的缺點
CAS的一個重要缺點在于如果忙等待一直執(zhí)行不成功(一直在死循環(huán)中),會對CPU造成較大的執(zhí)行開銷萨赁。
另外弊琴,代碼中,如果N個線程同時執(zhí)行到 singleton = new Singleton();的時候杖爽,會有大量對象被創(chuàng)建敲董,可能導(dǎo)致內(nèi)存溢出。
Q:好的慰安,除了使用CAS以外腋寨,你還知道有什么辦法可以不使用synchronized實現(xiàn)單例嗎?
A:這回真的不太知道了化焕。
Q:(那我再提醒他一下吧)可以考慮下ThreadLocal萄窜,看看能不能實現(xiàn)?
(面試者沒有思路的時候撒桨,我?guī)缀醵紩茸鲆幌绿嵝巡榭蹋瑢嵲跊]有思路再換下一個問題)
A:ThreadLocal?這也可以嗎凤类?
Q:你先說下你理解的ThreadLocal是什么吧
(通過他的回答穗泵,貌似對這個思路有些疑惑,不著急谜疤。先問一個簡單的問題佃延,讓面試者放松一下现诀,找找自信,然后再繼續(xù)問)
ThreadLoacal
ThreadLocal會為每一個線程提供一個獨立的變量副本履肃,從而隔離了多個線程對數(shù)據(jù)的訪問沖突赶盔。對于多線程資源共享的問題,同步機制(synchronized)采用了“以時間換空間”的方式榆浓,而ThreadLocal采用了“以空間換時間”的方式于未。
同步機制僅提供一份變量,讓不同的線程排隊訪問陡鹃,而ThreadLocal為每一個線程都提供了一份變量烘浦,因此可以同時訪問而互不影響。
Q:那理論上是不是可以使用ThreadLocal來實現(xiàn)單例呢萍鲸?
A:應(yīng)該也是可行的闷叉。
使用ThreadLocal實現(xiàn)單例模式:
public class Singleton {
private static final ThreadLocal<Singleton> singleton =
new ThreadLocal<Singleton>() {
@Override
protected Singleton initialValue() {
return new Singleton();
}
};
public static Singleton getInstance() {
return singleton.get();
}
private Singleton() {}
}
Q:嗯嗯,好的脊阴,那有關(guān)單例模式的實現(xiàn)的問題我就問的差不多了握侧。
(ThreadLocal這種寫法主要是考察面試者對于ThreadLocal的理解,以及是否可以把知識活學(xué)活用嘿期,但是實際上品擎,這種所謂的"單例",其實失去了單例的意義...)
(但是說實話备徐,能回答到這一題的人很少萄传,大多數(shù)面試者基本上在前面幾道題就已經(jīng)沒有思路了,大多數(shù)情況下根本不會問到這個問題就要改方向了)
A:(心中竊喜)嗯嗯蜜猾,學(xué)習到很多秀菱,感謝
Q:那...你知道如何破壞單例嗎?
(單例問題蹭睡,必問的一個衍菱。通過這個引申到序列化和反射的相關(guān)知識)
A:(額....)