面試官有毒吧?讓實現線程安全的單例,又不讓使用synchronized

單例模式,是Java中比較常見的一個設計模式,也是我在面試時常常會問到的一個問題。面試

 

通過個人初步統計,基本上有60%左右的人能夠說出2-4種單例的實現方式,有40%左右的人能夠說出5-6種單例的實現方式,只有20%左右的人可以說出7種單例的實現。算法

 

而只有不到1%的人可以說出7種以上的單例實現。設計模式

 

其實,做爲面試官,我大多數狀況下之因此問單例模式,是由於這個題目能夠問到不少知識點。安全

 

好比線程安全、類加載機制、synchronized的原理、volatile的原理、指令重排與內存屏障、枚舉的實現、反射與單例模式、序列化如何破壞單例、CAS、CAS的ABA問題、Threadlocal等知識。微信

 

通常狀況下,只須要從單例開始問起,大概就能夠完成一場面試的整個流程,把我想問的東西都問完,能夠比較全面的瞭解一個面試者的水平。多線程

 

如下,是一次面試現場的還原,從單例模式開始:模塊化

 

Q:你知道怎麼不使用synchronized和lock實現一個線程安全的單例嗎?學習

A:我知道,可使用"靜態內部類"實現。線程

 

靜態內部類實現單例模式:設計

 

面試官有毒吧?讓實現線程安全的單例,又不讓使用synchronized

 

Q:除了靜態內部類還會其餘的方式嗎?

A:還有就是兩種餓漢模式。

 

餓漢實現單例模式:

 

面試官有毒吧?讓實現線程安全的單例,又不讓使用synchronized

 

 

餓漢變種實現單例模式:

 

面試官有毒吧?讓實現線程安全的單例,又不讓使用synchronized

 

Q:那你上面提到的幾種都是線程安全的嗎?

A:是線程安全的

Q:那是如何作到線程安全的呢?

A:應該是由於我使用了static,而後類加載的時候就線程安全了吧?

Q:其實你說的並不徹底對,由於以上幾種雖然沒有直接使用synchronized,可是也是間接用到了。

(這裏面根據回答狀況會朝兩個不一樣的方向展開:一、類加載機制、模塊化等;二、繼續深刻問單例模式)

 

類加載過程的線程安全性保證

以上的靜態內部類、餓漢等模式均是經過定義靜態的成員變量,以保證單例對象能夠在類初始化的過程當中被實例化。

 

這實際上是利用了ClassLoader的線程安全機制。ClassLoader的loadClass方法在加載類的時候使用了synchronized關鍵字。

因此, 除非被重寫,這個方法默認在整個裝載過程當中都是線程安全的。因此在類加載過程當中對象的建立也是線程安全的。

 

Q:那還回到剛開始的問題,你知道怎麼不使用synchronized和lock實現一個線程安全的單例嗎?

(並非故意窮追不捨,而是但願能能夠引起面試者的更多思考)

A:額、、、那枚舉吧,枚舉也能夠實現單例。

 

枚舉實現單例模式:

 

面試官有毒吧?讓實現線程安全的單例,又不讓使用synchronized

 

Q:那你知道枚舉單例的原理嗎?如何保證線程安全的呢?

 

枚舉單例的線程安全問題

枚舉其實底層是依賴Enum類實現的,這個類的成員變量都是static類型的,而且在靜態代碼塊中實例化的,和餓漢有點像, 因此他自然是線程安全的。

 

Q:因此,枚舉其實也是藉助了synchronized的,那你知道哪一種方式能夠徹底不使用synchronized的嗎?

A:en....我想一想

Q:(過了一會他好像沒有思路)你知道CAS嗎?使用CAS能夠實現單例嗎?

(面試中,若是面試者對於鎖比較瞭解的話,那我大多數狀況下都會繼續朝兩個方向深刻問:一、鎖的實現原理;二、非鎖,如CAS、ThreadLocal等)

A:哦,我知道,CAS是一項樂觀鎖技術,當多個線程嘗試使用CAS同時更新一個變量時,只有其中一個線程能更新成功。

 

藉助CAS(AtomicReference)實現單例模式:

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實現的單例有沒有什麼優缺點呀?

A:用CAS的好處在於不須要使用傳統的鎖機制來保證線程安全,CAS是一種基於忙等待的算法,依賴底層硬件的實現,相對於鎖它沒有線程切換和阻塞的額外消耗,能夠支持較大的並行度。

Q:你說的好像是優勢?那缺點呢?

 

CAS實現的單例的缺點

CAS的一個重要缺點在於若是忙等待一直執行不成功(一直在死循環中),會對CPU形成較大的執行開銷。

 

另外,代碼中,若是N個線程同時執行到 singleton = new Singleton();的時候,會有大量對象被建立,可能致使內存溢出。

Q:好的,除了使用CAS之外,你還知道有什麼辦法能夠不使用synchronized實現單例嗎?

A:這回真的不太知道了。

Q:(那我再提醒他一下吧)能夠考慮下ThreadLocal,看看能不能實現?

(面試者沒有思路的時候,我幾乎都會先作一下提醒,實在沒有思路再換下一個問題)

A:ThreadLocal?這也能夠嗎?

Q:你先說下你理解的ThreadLocal是什麼吧

(經過他的回答,貌似對這個思路有些疑惑,不着急。先問一個簡單的問題,讓面試者放鬆一下,找找自信,而後再繼續問)

 

ThreadLoacal

ThreadLocal會爲每個線程提供一個獨立的變量副本,從而隔離了多個線程對數據的訪問衝突。對於多線程資源共享的問題,同步機制(synchronized)採用了「以時間換空間」的方式,而ThreadLocal採用了「以空間換時間」的方式。

 

同步機制僅提供一份變量,讓不一樣的線程排隊訪問,而ThreadLocal爲每個線程都提供了一份變量,所以能夠同時訪問而互不影響。

Q:那理論上是否是可使用ThreadLocal來實現單例呢?

A:應該也是可行的。

 

使用ThreadLocal實現單例模式:

 

面試官有毒吧?讓實現線程安全的單例,又不讓使用synchronized

 

Q:嗯嗯,好的,那有關單例模式的實現的問題我就問的差很少了。

(ThreadLocal這種寫法主要是考察面試者對於ThreadLocal的理解,以及是否能夠把知識活學活用,可是實際上,這種所謂的"單例",其實失去了單例的意義...)

(可是說實話,能回答到這一題的人不多,大多數面試者基本上在前面幾道題就已經沒有思路了,大多數狀況下根本不會問到這個問題就要改方向了)

A:(心中竊喜)嗯嗯,學習到不少,感謝

Q:那...你知道如何破壞單例嗎?

(單例問題,必問的一個。經過這個引伸到序列化和反射的相關知識)

A:(額....)

最後這裏小編整理了一套面試資料,讓你面試不慌張

 

面試官有毒吧?讓實現線程安全的單例,又不讓使用synchronized

 

面試官有毒吧?讓實現線程安全的單例,又不讓使用synchronized

 

面試官有毒吧?讓實現線程安全的單例,又不讓使用synchronized

 

領取步驟:
一、加微信便可免費領取

相關文章
相關標籤/搜索