單例,大家肯定都不陌生,這是Java中很重要的一個(gè)設(shè)計(jì)模式。稍微了解一點(diǎn)單例的朋友也都知道實(shí)現(xiàn)單例是要考慮并發(fā)問題的,一般情況下,我們都會(huì)使用synchronized來保證線程安全。
那么,如果有這樣一道面試題:不使用synchronized和lock,如何實(shí)現(xiàn)一個(gè)線程安全的單例?你該如何回答?
C類應(yīng)聘者:可以使用餓漢模式實(shí)現(xiàn)單例。如:
public class Singleton {
? ? private static Singleton instance = new Singleton();
? ? private Singleton (){}
? ? public static Singleton getInstance() {
? ? ? return instance;
? ? }
}
還有部分程序員可以想到餓漢的變種:
public class Singleton {
? ? private Singleton instance = null;
? ? static {
instance = new Singleton();
? ? }
? ? private Singleton (){}
? ? public static Singleton getInstance() {
? ? ? ? return this.instance;
? ? }
}
使用static來定義靜態(tài)成員變量或靜態(tài)代碼,借助Class的類加載機(jī)制實(shí)現(xiàn)線程安全單例。
面試官:除了這種以外,還有其他方式嗎?
B類應(yīng)聘者:
除了以上兩種方式,還有一種辦法,就是通過靜態(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;
}
}
這種方式相比前面兩種有所優(yōu)化,就是使用了lazy-loading。Singleton類被裝載了,但是instance并沒有立即初始化。因?yàn)镾ingletonHolder類沒有被主動(dòng)使用,只有顯示通過調(diào)用getInstance方法時(shí),才會(huì)顯示裝載SingletonHolder類,從而實(shí)例化instance。
面試官:除了這種以外,還有其他方式嗎?
A類應(yīng)聘者:
除了以上方式,還可以使用枚舉的方式,如:
public enum Singleton {
INSTANCE;
? ? public void whateverMethod() {
}
}
這種方式是Effective Java作者Josh Bloch 提倡的方式,它不僅能避免多線程同步問題,而且還能防止反序列化重新創(chuàng)建新的對(duì)象,可謂是很堅(jiān)強(qiáng)的壁壘。
面試官:以上幾種答案,其實(shí)現(xiàn)原理都是利用借助了類加載的時(shí)候初始化單例。即借助了ClassLoader的線程安全機(jī)制。
所謂ClassLoader的線程安全機(jī)制,就是ClassLoader的loadClass方法在加載類的時(shí)候使用了synchronized關(guān)鍵字。也正是因?yàn)檫@樣, 除非被重寫,這個(gè)方法默認(rèn)在整個(gè)裝載過程中都是同步的,也就是保證了線程安全。
所以,以上各種方法,雖然并沒有顯示的使用synchronized,但是還是其底層實(shí)現(xiàn)原理還是用到了synchronized。
面試官:除了這種以外,還有其他方式嗎?
A類應(yīng)聘者:
還可以使用Java并發(fā)包中的Lock實(shí)現(xiàn)
面試官:本質(zhì)上還是在使用鎖,不使用鎖的話,有辦法實(shí)現(xiàn)線程安全的單例嗎?
A+類面試者:
有的,那就是使用CAS。
CAS是項(xiàng)樂觀鎖技術(shù),當(dāng)多個(gè)線程嘗試使用CAS同時(shí)更新同一個(gè)變量時(shí),只有其中一個(gè)線程能更新變量的值,而其它線程都失敗,失敗的線程并不會(huì)被掛起,而是被告知這次競(jìng)爭(zhēng)中失敗,并可以再次嘗試。實(shí)現(xiàn)單例的方式如下:
public class Singleton {
? ? private static final AtomicReference INSTANCE = 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;
? ? ? ? ? ? }
? ? ? ? }
? ? }
}
面試官:這種方式實(shí)現(xiàn)的單例有啥優(yōu)缺點(diǎn)嗎?
A++類面試者:
用CAS的好處在于不需要使用傳統(tǒng)的鎖機(jī)制來保證線程安全,CAS是一種基于忙等待的算法,依賴底層硬件的實(shí)現(xiàn),相對(duì)于鎖它沒有線程切換和阻塞的額外消耗,可以支持較大的并行度。
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)存溢出。
面試官:你被錄取了!
------END
最后附上筆者創(chuàng)建的一個(gè)java技術(shù)交流群,歡迎大家進(jìn)群交流java相關(guān)的技術(shù),群主會(huì)不定時(shí)發(fā)紅包,組織抽獎(jiǎng),獎(jiǎng)品是下面幾本書之一:
從paxos到zookeeper分布式一致性原理與實(shí)踐? ? 作者:倪超
Redis設(shè)計(jì)與實(shí)現(xiàn)? ? 作者:黃建宏
kafka源碼分析? ?
分布式系統(tǒng)架構(gòu)設(shè)計(jì)與實(shí)現(xiàn)
高性能mysql
Innodb引擎原理分析
還有幾本,篇幅限制就不一一列舉了
掃碼加群,享受美團(tuán),阿里,頭條內(nèi)推福利
注意:想去其他互聯(lián)網(wǎng)大廠的勿擾,目前只有美團(tuán),阿里,頭條的內(nèi)推通道
