面試官所認爲的單例模式

單例模式是23種GOF模式中最簡單,也是最常常出現的一種設計模式,也是面試官最常愛考的一種模式,爲何呢? 由於單例模式足夠簡單,編寫一個單例模式代碼幾分鐘就能搞定,因此設計模式中面試官一般會選取單例模式做爲出題。 下面把單例模式分幾個點,分別說說哪些地方面試官能考你?java

單例模式的意義

一般面試官會很籠統的問你,什麼是單例模式?單例模式用來解決了什麼痛點?沒有單例模式咱們會怎麼辦?單例模式他有什麼缺點嗎?git

單例模式是最簡單的設計模式之一,屬於建立型模式,它提供了一種建立對象的方式,確保只有單個對象被建立。這個設計模式主要目的是想在整個系統中只能出現類的一個實例,即一個類只有一個對象。 單例模式的解決的痛點就是節約資源,節省時間從兩個方面看:github

1.因爲頻繁使用的對象,能夠省略建立對象所花費的時間,這對於那些重量級的對象而言,是很重要的.面試

2.由於不須要頻繁建立對象,咱們的GC壓力也減輕了,而在GC中會有STW(stop the world),從這一方面也節約了GC的時間 單例模式的缺點:簡單的單例模式設計開發都比較簡單,可是複雜的單例模式須要考慮線程安全等併發問題,引入了部分複雜度。設計模式

擴展:從你的回答中能進行哪些擴展呢?咱們談到了GC,有可能這時候就會問你GC,STW等知識。談缺點的時候談到了複雜的單例模式, 這個時候可能會問你讓你設計一個優秀的單例模式你會怎麼設計,會怎麼實現?安全

單例模式的設計

一般這裏面試官會問你單例模式怎麼設計,須要看重哪些方面?通常來講單例模式有哪些實現方式?bash

設計單例模式的時候通常須要考慮幾種因素:多線程

-線程安全 -延遲加載 -代碼安全:如防止序列化攻擊,防止反射攻擊(防止反射進行私有方法調用) -性能因素併發

通常來講,咱們去網上百度去搜大概有7,8種實現,,下面列舉一下須要重點知道的 餓漢,懶漢(線程安全,線程非安全),雙重檢查(DCL)(重點),內部類,以及枚舉(重點), 下面比對下各個實現:函數

 

線程安全

併發性能好

能夠延遲加載

序列化/反序列化安全

能抵禦反射攻擊

餓漢式

Y

Y

 

 

 

懶漢式

不加鎖

 

Y

Y

 

 

加鎖的

Y

 

Y

 

 

DCL

Y

Y

Y

 

 

靜態內部類

Y

Y

Y

 

 

枚舉

Y

Y

 

Y

Y

擴展:咱們上面說到了各個模式的實現,這個時候頗有可能會叫你手寫各個模式的代碼。固然也有可能會問你線程安全,代碼安全等知識。

餓漢模式

餓漢模式的代碼以下:

public class Singleton {
    private static Singleton instance = new Singleton();
    private Singleton(){}
    public static Singleton getInstance(){
        return instance;
    }
}
複製代碼

餓漢模式代碼比較簡單,對象在類中被定義爲private static,經過getInstance(),經過java的classLoader機制保證了單例對象惟一。 擴展:

有可能會問instance何時被初始化?

Singleton類被加載的時候就會被初始化,java虛擬機規範雖然沒有強制性約束在何時開始類加載過程,可是對於類的初始化,虛擬機規範則嚴格規定了有且只有四種狀況必須當即對類進行初始化,遇到new、getStatic、putStatic或invokeStatic這4條字節碼指令時,若是類沒有進行過初始化,則須要先觸發其初始化。 生成這4條指令最多見的java代碼場景是:1)使用new關鍵字實例化對象2)讀取一個類的靜態字段(被final修飾、已在編譯期把結果放在常量池的靜態字段除外)3)設置一個類的靜態字段(被final修飾、已在編譯期把結果放在常量池的靜態字段除外)4)調用一個類的靜態方法

class的生命週期?

class的生命週期通常來講會經歷加載、鏈接、初始化、使用、和卸載五個階段

class的加載機制

這裏能夠聊下classloader的雙親委派模型。

雙重檢查DCL

public class Singleton {  
    private volatile static Singleton singleton;  
    private Singleton (){}  
    public static Singleton getSingleton() {  
    if (singleton == null) {  
        synchronized (Singleton.class) {  
        if (singleton == null) {  
            singleton = new Singleton();  
        }  
        }  
    }  
    return singleton;  
    }  
}  
複製代碼

synchronized同步塊裏面可以保證只建立一個對象。可是經過在synchronized的外面增長一層判斷,就能夠在對象一經建立之後,再也不進入synchronized同步塊。這種方案不只減少了鎖的粒度,保證了線程安全,性能方面也獲得了大幅提高。

同時這裏要注意必定要說volatile,這個很關鍵,volatile通常用於多線程的可見性,可是這裏是用來防止指令重排序的。

擴展:

爲何須要volatile?volatile有什麼用?

  • 首先要回答可見性,這個是毋庸質疑的,而後可能又會考到java內存模型。
  • 防止指令重排序: 防止new Singleton時指令重排序致使其餘線程獲取到未初始化完的對象。instance = new Singleton()這句,這並不是是一個原子操做,事實上在 JVM 中這句話大概作了下面 3 件事情。1.給 instance 分配內存2.調用 Singleton 的構造函數來初始化成員變量3.將instance對象指向分配的內存空間(執行完這步 instance 就爲非 null 了) 可是在 JVM 的即時編譯器中存在指令重排序的優化。也就是說上面的第二步和第三步的順序是不能保證的,最終的執行順序多是 1-2-3 也多是 1-3-2。若是是後者,則在 3 執行完畢、2 未執行以前,被線程二搶佔了,這時 instance 已是非 null 了(但卻沒有初始化),因此線程二會直接返回 instance,而後使用,而後報錯。
  • 順便也能夠說下volatie原理用內存屏障

講講synchronized和volatile的區別

這裏能夠從synchroized能保證原子性,volatile不能保證提及,以及講下synchroized是重量級鎖,甚至能夠因此下他和Lock的區別等等。

線程安全通常怎麼實現的?

  • 互斥同步。如lock,synchroized
  • 非阻塞同步。如cas。
  • 不一樣步。如threadLocal,局部變量。

枚舉類

public enum Singleton{
    INSTANCE;
}
複製代碼

默認枚舉實例的建立是線程安全的,因此不須要擔憂線程安全的問題。同時他也是《Effective Java》中推薦的模式。最後經過枚舉類,他能自動避免序列化/反序列化攻擊,以及反射攻擊(枚舉類不能經過反射生成)。

總結

單例模式雖然看起來簡單,可是設計的Java基礎知識很是多,如static修飾符、synchronized修飾符、volatile修飾符、enum等。這裏的每個知識點均可以變成面試官下手的考點,而單例只是做爲一個引子,考到最後看你到底掌握了多少。看你的廣度和深度究竟是怎麼樣的。

最後這篇文章被我收錄於JGrowing,一個全面,優秀,由社區一塊兒共建的Java學習路線,若是您想參與開源項目的維護,能夠一塊兒共建,github地址爲:github.com/javagrowing… 麻煩給個小星星喲。

若是你們以爲這篇文章對你有幫助,或者想提早獲取後續章節文章,或者你有什麼疑問想提供1v1免費vip服務,均可以關注個人公衆號你的關注和轉發是對我最大的支持,O(∩_∩)O:

相關文章
相關標籤/搜索