單例模式相信是不少程序員接觸最多的了,也是面試過程當中考察最頻繁的一個了,不知道你有沒有被問過這道面試題?歡迎留言討論。java
今天咱們來重點討論一下單例的幾個問題,及如何正確的實現一個單例,而後你再來回顧一下,你以前的回答或者使用方式是否正確。程序員
單例很是簡單,一個類只容許建立一個對象或者實例,這個類就是一個單例類。這種設計模式就叫作單例設計模式,是建立型的第一種設計模式,簡稱單例模式。面試
單例模式何時使用呢?又或者說這種狀況下爲何要使用單例?設計模式
全局惟一類
有時候,咱們作業務設計時,有些數據在系統中只應該保留一份,這時候就應該設計爲單例。
好比配置信息類,系統的配置文件應該只有一份,加載到內存以後以對象的形式存在,理所應當只有一份。
再好比說,咱們設計一個抽獎系統,每點擊一次生成一個抽獎序號,能夠設計一個單例,內部存儲好全部的序號,每次隨機取出一個序號。若是使用普通類對象的話,那就須要經過共享內存共享全部抽獎序號。安全
學習任何東西,由於大腦的容量是有限的,首先咱們要理解概念,知道爲何,來後追求怎麼作,怎麼實現,作的過程可能很複雜,好比有一二三四五步驟,但咱們要化繁爲簡,歸納精簡。多線程
單例須要考慮如下幾個問題:併發
經過這種形容方式,能夠直觀的理解一下,餓漢一直擔憂本身吃不飽,因此先吃了再說,也就是說實例是事先初始化好的,也就沒有辦法延遲加載了。
不支持懶加載,有人就說這種方式很差,說我都沒有使用單例,你都給我加載了,浪費啊。可是有壞處也有好處,提早把類加載進來,提早暴露問題,這樣若是類的設計有問題,在程序啓動時就會報錯,而不是等到程序運行中才暴露出來。分佈式
public class SingleTon { private static final SingleTon instance = new SingleTon(); private SingleTon() {} public static SingleTon getInstance() { return instance; } public void method() {} }
所謂懶漢式,那就是支持延遲加載嘍。整體思路相似,但在類內部並非默認就把instance實例化好。函數
public class SingleTon { private static SingleTon instance; private SingleTon() {} public static synchronized SingleTon getInstance() { if (instance == null) { instance = new SingleTon(); } return instance; } public void method() {} }
爲何要加synchronized呢?若是是多線程同時調用getInstance(),會有併發問題啊,多個線程可能同時拿到instance == null的判斷,這樣就會重複實例化,單例就不是單例。因此爲了解決多線程併發的問題,這裏犧牲了性能,變成了嚴格的串行制。多線程下性能很低。性能
餓漢方式不支持延遲加載。
懶漢方式,多線程下性能低下,那怎麼修改呢,就是改進的懶漢方式,又叫雙重檢測。
具體怎麼作呢?
public class SingleTon { private static volatile SingleTon instance; private SingleTon() {} public static SingleTon getInstance() { if (instance == null) { synchronized (SingleTon.class) { if (instance == null) { instance = new SingleTon(); } } } return instance; } public void method() {} }
這個類裏的volatile十分關鍵,若是沒有volatile關鍵字修飾instance變量,若是線程1執行到instance = new SingleTon();的時候,線程2此時判斷instance已經不等於null了,會直接返回instance,但此時instance並未初始化完畢,爲何這麼說呢?由於對象的初始化分爲三步:
既然是分爲三步,那就不是原子操做,並且可能會發生指令重排,也就是說可能先執行第三步,這時候其餘線程判斷instance也就不是null了。加上volatile關鍵字,能夠禁止機器指令重排,就不會有這個問題了。
這種方式,避免雙重檢測,利用java靜態內部類,相似餓漢方式,又作到延遲加載。
public class SingleTon { private SingleTon() {} private static class SingleTonHolder { private static final SingleTon instance = new SingleTon(); } public static SingleTon getInstance() { return SingleTonHolder.instance; } public void method() {} }
是否是以爲很簡潔?推薦你們使用這種方式,類SingleTon加載時,並不會加載SingleTonHolder類,只要調用getInstance方法時,SingleTonHolder纔會被加載,並建立instance,這些都是由JVM來保證的。
還有一種更簡單的,可是理解起來可能有點費解,枚舉的構造函數默認就是私有的。java的枚舉類型自己就保證了線程安全性和實例惟一性。
只須要簡單幾行,就可使用枚舉單例INSTANCE的方法了。
public enum SingleTon { INSTANCE; public void method() {} }
可是單例模式真的就好嗎?下面咱們會討論一下爲何不推薦單例模式?如何替代,以及如何作到集羣下的分佈式單例模式?
程序員的小夥伴們,學習之路,同行的人越多才能夠走的更遠,加入公衆號[程序員之道],一塊兒交流溝通,走出咱們的程序員之道!