對於一個軟件系統中的某些類而言,只有一個實例是很重要的。單例模式(Singleton)是結構最簡單的設計模式,它的核心結構中只包含一個被稱爲單例類的特殊類。單例模式是一種對象建立型模式。實現單例模式有3個要點:html
單例模式(Singleton)定義:確保一個類只有一個實例,並提供一個全局訪問點來訪問這個惟一實例。java
單例模式實現面試
對於單例模式(Singleton),在單例類的內部建立它的惟一實例,並經過靜態方法getInstance()讓客戶端可使同它的惟一實例;爲了防止在外部對單例類實例化,將其構造函數的設置爲private;在單例類內部定義一個Singleton類型的靜態對象做爲供外部共享訪問的惟一實例。設計模式
在單例模式的實現過程當中須要注意如下3點:緩存
(1) 單例類的構造函數的訪問權限爲private安全
(2) 提供一個類型爲自身的靜態私有成員變量多線程
(3) 提供一個公有的靜態工廠方法。併發
1、餓漢式單例函數
餓漢式單例類中定義了一個靜態變量,當類被加載時,靜態變量就會被初始化,此時類的私用構造函數會被調用,單例類的惟一實例將被建立。代碼:高併發
public class EagerSingleton { private static final EagerSingleton instance = new EagerSingleton(); private EagerSingleton(){ } public static EagerSingleton getInstance(){ return instance; } }
2、懶漢式單例
懶漢式單例與餓漢式單例不一樣的是採用了延遲加載(Lazy Load)技術,即須要的時候再加載實例。代碼:
public class LazySingleton { private static LazySingleton instance = null; private LazySingleton(){ } /** * 使用synchronize關鍵字對方法加鎖,確保任意時刻都只有一個線程能夠執行該方法 * @return */ synchronized public static LazySingleton getInstance() { if (instance == null){ instance = new LazySingleton(); } return instance; } }
上述餓漢式單例類中,在getInstance()方法前面增長了synchronized關鍵字,以處理多線程同時訪問的的問題,可是在多線程高併發訪問的環境中將會致使系統性能大大下降。所以的對該代碼進行改進,能夠發現,咱們在代碼中無需對整個方法進行加鎖,只需用synchronized對 instance = new LazySingLeton() 進行鎖定便可。
(有關synchronized詳解,能夠參考: 《java 鎖機制(synchronized 與 Lock)》)
代碼以下:
public class LazySingleton { private static LazySingleton instance = null; private LazySingleton(){ } public static LazySingleton getInstance() { if (instance == null){ synchronized (LazySingleton.class){ instance = new LazySingleton(); } } return instance; } }
以上代碼即實現了單例模式,貌似又解決了多線程高併發的問題,然而事實並不是如此,咱們來分析一下。假如,線程A和線程B都在同一實際調用getInstance() 方法,此時instance對象爲null,線程A、線程B同時進入if (instance == null) {} 代碼塊,假如A先獲取鎖,進行實例建立,當A執行完畢後,B獲取鎖,也會進行實例建立,致使產生了多個單例對象,違背了單例模式的設計原則。怎麼解決呢?咱們能夠在synchronized代碼塊中再次進行 instance == null 的判斷,這種方式稱爲雙重檢查鎖定(Double-Check Locking)。代碼以下:
public class LazySingleton { private static volatile LazySingleton instance = null; private LazySingleton(){ } public static LazySingleton getInstance() { if (instance == null){ synchronized (LazySingleton.class){ // 二重判斷 if (instance == null){ instance = new LazySingleton(); } } } return instance; } }
注意:細心的小夥伴會在上面代碼中發現,咱們在定義類變量時多了一個volatile 關鍵字,被volatile關鍵字修飾的成員變量能夠確保多個線程都可以正確處理。而且,volatile關鍵字會屏蔽Java虛擬機自動的代碼優化,可能會致使系統的運行效率下降,所以即便使用雙重檢查鎖定來實現單例模式也不是一種完美的實現方式。
volatile:用以聲明變量的值可能隨時會別的線程修改,使用volatile修飾的變量會強制將修改的值當即寫入主存,主存中值的更新會使緩存中的值失效(非volatile變量不具有這樣的特性,非volatile變量的值會被緩存,線程A更新了這個值,線程B讀取這個變量的值時可能讀到的並非是線程A更新後的值)。volatile會禁止指令重排,且是內存可見的。
volatile特性:volatile具備可見性、有序性,不具有原子性。
注意:volatile不具有原子性,這是volatile與java中的synchronized、Lock最大的功能差別,這一點在面試中也是很是容易問到的點。
可見性、有序性、原子性:
1.原子性:指多個操做要麼所有執行,要麼不執行。
2.可見性:當多個線程訪問同一個變量x時,線程1修改了變量x的值,線其餘可以當即讀取到線程1修改後的值。
3.有序性:值程序執行時按照代碼編寫的前後順序執行。
3、使用IoDH實現單例
餓漢式單例類不能實現延遲加載,無論未來用不用始終佔據內存;懶漢式單例類採用synchronized來解決線程安全問題,在性能上會對系統有必定影響。可見,不管是餓漢式仍是懶漢式都會存在一些問題。爲了解決這些問題,在java語言中能夠經過Initialization on Demand Holder(IoDH)技術類實現單例模式。
在IoDH中,須要在單例類中增長一個靜態內部類(static class),在該內部類中建立單例對象,在將該單例對象經過getInstance() 方法返回給外部內使用,代碼以下:
public class Singleton { private Singleton(){ } private static class IoDHSingleton { private static final Singleton instance = new Singleton(); } public static Singleton getInstance() { return IoDHSingleton.instance; } }
運行機制:因爲靜態單例對象沒有做爲Singleton的成員變量直接實例化,所以類加載時不會實例化Singleton,第一次調用getInstance() 時將加載內部類IoDHSingleton,在該內部類中定義了一個static類型的變量instance,此時會首先初始化這個static成員變量,由java虛擬機自動保證線程安全問題,確保該成員變量只能別初始化一次。
經過使用IoDH既能夠實現延遲加載,又能夠保證線程安全,不影響系統性能,不失爲java中最好的單例實現方式。
單例模式的缺點
在如今不少面嚮對象語言的運行環境中,不少都提供了垃圾自動回收技術,所以若是實例的共享對象長時間不被利用,系統會自動銷燬並回收資源,下次利用又將從新實例化,這將致使共享的單例對象狀態的丟失。