設計模式-單例模式

《巫師3》中,陪著主人公南征北戰的坐騎,不管你何時何地召喚它,它永遠只有一個名字——蘿卜,
大家好,我是左耳朵梵高,文章首發于微信公眾號「左耳朵梵高」,歡迎關注,和我一起持續學習,終身成長, ---- 生活不只眼前的茍且,還有詩和遠方,
面試開始
HR :來了一個面試Java的,我讓他在小會議室等著了,
面試官 :好的,我就來,
面試官用一次性紙杯倒了杯水,夾著Mac,進了小會議室,看見一個20出頭的精神小伙,帶著黑框眼鏡,發量誘人,像極了N年前的自己,風華正茂,書生意氣,
面試官 :你好,先喝杯水吧,(不給應聘者倒水的公司都是不靠譜的)我看你簡歷上寫著精通設計模式,要不我們就聊聊設計模式吧,
應聘者 :可以呀,
一句輕描淡寫的“可以呀”,但經驗豐富的面試官還是發現了平靜面容下,應聘者的一絲絲竊喜,好像很胸有成竹的樣子,
面試官 :那就說說,你平時都用了哪些設計模式吧?
應聘者 :(內心狂喜ing)我平時使用最多的設計模式有單例模式,單例模式屬于23種設計模式中的創建型的設計模式,23種設計模式可以分為3種:創建型、結構型和行為型,單例模式確保了一個類只有一個實體,單例模式有5種實作方式:懶漢式、餓漢式、Double-Check方式、靜態內部類方式、列舉方式,
面試官 :嗯,你對單例模式了解的不錯嘛,你先說下為什么要使用單例模式吧,
應聘者 :單例模式其實很簡單,就是一個類只能創建一個實體,在程式中,有一些物件只需要一個,比如說:執行緒池、快取、對話框、注冊表、日志物件、充當列印機、顯卡等設備驅動程式的物件,事實上,這一類物件只能有一個實體,如果制造出多個實體就可能會導致一些問題的產生,比如:程式的行為例外、資源使用過量、或者不一致性的結果,還有些業務上就只會有一個,比如公司主體等,
面試官 :那如何實作一個單例呢?
應聘者 :實作單例有好幾種方式,有餓漢式、懶漢式、靜態內部類,或者使用列舉來實作,使用單例模式,一般把類的建構式設定為private,避免通過new創建多個示例,我先來說下餓漢方式吧,
應聘者喝了口水,似乎準備開始表演了,
應聘者 :餓漢式實作比較簡單,類有一個靜態的實體,一般取名為instance,在類加載的時候,就會創建并初始化好instance實體,所以,餓漢式是執行緒安全的,
面試官 :你能寫一下具體的實作代碼嗎?
應聘者很快就在紙上寫出了餓漢式的代碼實作:
public static Singleton{
private static final Singleton = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return instance;
}
}
看的出來,應聘者對餓漢式的代碼實作很熟悉,編碼風格和命名也很不錯,我在面試的時候,就有幾位應聘者不知道如何給類命名,有的使用Danli,有的使用Single或One,
面試官 :嗯,很不錯,你平時都使用這種方式嗎?
應聘者 :哦,不是的,餓漢式雖然簡單,但是有個問題是,它不支持延遲加載,或者叫按需加載,在系統啟動時,就必須要創建實體,
面試官 :這樣會有什么問題嗎?
應聘者 :如果實體占用資源多,比如記憶體占用高,或者初始化耗時長(比如需要加載各種組態檔),提前初始化就會造成浪費,應該在用到的時候再去初始化,
面試官 :如果初始化耗時長,等用到的時候再初始化,就可能在用戶請求介面的時候,觸發了這個初始化程序,會導致請求的回應時間很長,甚至超時,對用戶造成影響,所以,究竟是啟動時初始化好,還是延遲初始化好呢?
應聘者 :啊,這個,,,(這個面試官不按套路出牌呀)網上說的都是要延遲加載,
面試官 :還有,如果實體占用資源多,比如記憶體使用高,如果延遲加載,可能會出現在程式運行一段時間后,因為初始化實體,占用資源多,出現了OOM,程式崩潰,根據Fast Fail原則,是不是就應該在啟動時初始化實體,如果資源不夠,我們就能快速發現問題,盡快進行修復,而不會讓問題在生產環境中才暴露,
應聘者 :嗯,好像有道理,但我看網上的文章都說這種方式不好,
面試官 :那你覺得哪種方式好呢?
應聘者 :(內心有些搖擺,有些凌亂)
面試官 :那我們再聊聊延遲加載的單例?
應聘者 :嗯嗯,好呀,延遲加載就是在使用的時候才進行初始化,它的代碼實作是這樣的:
public class Singleton{
private static Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
if (instance == null){
synchronized(Singleton.class){
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
面試官 :嗯,不錯嘛,我看getInstance方法中,有多個null判斷,還有個synchronized鎖,能不能解釋一下,
應聘者 :這個叫雙重檢查(Double Check),加synchronized是為了保證執行緒安全,null判斷是為了提升性能,如果不在前面先判斷instance是否為null,就需要在每次使用時,先獲取鎖,然后釋放鎖,會導致性能瓶頸,
應聘者 :所以使用了雙重檢查,只要instance被創建后,即使再呼叫getInstance,也不會再加鎖了,解決了性能問題,
應聘者 :網上有人說,這種實作方式也有問題,因為指令重排,可能會導致Singleton被new出來后,被賦值給了instance,還沒來得及初始化,就被另一個執行緒使用了,可能會出現NPE錯誤,要解決這個問題,我們需要給instance成員變數添加volatile關鍵字,禁止指令重排,
面試官 :嗯,你對Java指令重排也有了解呀,不錯,關于執行緒安全,我們稍后再仔細聊聊吧,
應聘者 :(不要啊,我就只記住了這一段,待會兒一聊就露餡了啊,,,)
面試官:你知道還有其它實作單例的方式嗎?
應聘者 :還有個靜態內部類方式,它比雙重檢查更加簡單,就是利用Java的靜態內部類,代碼實作是這樣的:
public class Singleton{
private Singleton(){}
private static class SingletonHolder{
private static final Singleton instance = new Singleton();
}
public static Singleton getInstance(){
return SingletonHolder.instance;
}
}
應聘者 :SingletonHolder是一個內部靜態類,當外部Singleton被加載時,并不會創建SingletonHolder實體物件,只有當呼叫getInstance方法時,SingletonHolder才被加載,這個時候才會創建instance,instance的唯一性、創建程序的執行緒安全有JVM虛擬機來保證,所以,這種實作方法既保證了執行緒安全,又能做到延遲加載,
應聘者 :還有一種使用列舉創建單例的方式,
面試官 :哇,還有嘛,那你再說說吧,
應聘者 :使用列舉應該是最簡單的,它利用了Java列舉型別本身的特點,保證了實體創建的執行緒安全和實體唯一性,代碼如下:
public enum Singleton{
INSTANCE;
}
面試官 :你平時都是使用這個方式嗎?
應聘者 :沒有呢,這種方式的確簡單,而且也是《Effective Java》作者推薦的,但是我覺得用列舉來表達一個單例,這種方式比較奇怪,總覺得是一樣投機取巧的方式,
面試官 :哈哈哈,,的確是這樣,開源專案中也很少會使用這種方式,是比較怪,你對單例模式的理解很深入呀,說出了這么多種實作,不錯不錯,剛才看你對執行緒安全也挺了解的,那我們接下來再聊聊Java多執行緒吧,
應聘者 :(狠狠抽了幾下耳巴子,,,叫你多嘴,,,)
重點回顧
單例模式是面試中經常出現的話題,單例模式本身比較簡單,就是一個類只有一個實體,大部分面試者在面試準備時,都會閱讀單例的相關知識點,比如單例模式的多種實現,
但是,希望大家不要僅僅是背誦,還應該多去理解,本文的面試中,面試官問了一個問題,到底是啟動時初始化好,還是延遲加載好呢?這個問題,大家可以自己思考一下,

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/301416.html
標籤:其他
上一篇:《我是面試官》設計模式-單例模式
下一篇:Linux下rpm打包
