單例設計模式 單例設計模式是GOF23種設計模式中最簡單的一種設計模式,也是Java編程中入門級别的設計模式。其定義就是在某種情況下,一個類隻有一個實例,且該類能自行創建這個實例的一種模式。例如,Windows 中隻能打開一個任務管理器,這樣可以避免因打開多個任務管理器窗口而造成内存資源的浪費,或出現各個窗口顯示内容的不一緻等錯誤。
在計算機系統中,還有Windows的回收站、操作系統中的文件系統、多線程中的線程池、顯卡驅動、打印機的後台處理線程、應用程序的日志對象、數據庫連接池、網站計數器等等,在設計的時候都被設計成了單例模式。
用途 在實際開發中單例設計模式主要有如下的一些優勢
由于單例設計模式,在内存中隻存在一個對象實例,從内存消耗的角度上來講,尤其是對于一些頻繁的創建或者是銷毀實例的操作,是非常節省内存消耗的。避免了對于資源文件的多重占用,例如我們在使用打印機的時候,隻需要一個打印機控制程序即可,而不是對每個需要打印的内容都開啟一個打印機的控制程序,這樣如果打印的内容太多的話就會造成資源的浪費。 在實際開發中,在有些業務中可能是因為業務的需求,所以必須要采用到單例設計模式。
實現單例設計模式 在之前的文章中我們介紹過關于單例設計模式,同時我們介紹了單例設計模式的設計原則
一個私有的構造方法在類體中創建好一個對象在類體中創建一個提供對外獲取該對象的方法。 可以簡單的将其理解為成員變量私有化,并且提供get方法。下面我們就來看看具體的單例設計模式有哪些實現方式。
餓漢式 餓漢式實現方式作為在我們實現單例模式中最為常用的一種實現方式,其代碼如下。
public class Singleton { //在類内部實例化一個實例 private static Singleton instance = new Singleton(); //私有的構造函數,外部無法訪問 private Singleton() { } //對外提供獲取實例的靜态方法 public static Singleton getInstance() { return instance; } }
所謂的餓漢式實現方式顧名思義就是說當你需要使用這個對象的時候你就可以立即獲取到該對象,而不需要等待的時間,所以在創建對象的時候後采用的static關鍵字來進行初始化,這樣可以保證在類被第一次加載的時候對象就會被創建,也就會保證在後續訪問的時候可以直接獲取到該對象。
由于該對象的創建是在類初始化的時候就被創建的,所以也就避免了後續的線程安全相關的問題。也就是說餓漢式設計是天然的線程安全、調用效率高、不加延遲操作。下面這種方式是餓漢式的一種變種,實現的效果與上面的代碼實現的效果是一樣的。
public class Singleton2 { //在類内部定義 private static Singleton2 instance; static { //實例化該實例 instance = new Singleton2(); } //私有的構造函數,外部無法訪問 private Singleton2() { } //對外提供獲取實例的靜态方法 public static Singleton2 getInstance() { return instance; } }
對于餓漢式單例模式,是在類被加載的時候對象就進行了實例化,所以就會造成一些不必要的内存消耗,因為可能被初始化的實例對象在整個的代碼周期中就用不到,而且如果這個類被多次加載的話也會出現多個實例的情況,為了解決這個問題,就有了下面這種方式。
靜态内部類式 靜态内部類解決餓漢式加載問題
public class StaticInnerClassSingleton { //在靜态内部類中初始化實例對象 private static class SingletonHolder { private static final StaticInnerClassSingleton INSTANCE = new StaticInnerClassSingleton(); } //私有的構造方法 private StaticInnerClassSingleton() { } //對外提供獲取實例的靜态方法 public static final StaticInnerClassSingleton getInstance() { return SingletonHolder.INSTANCE; } }
這種方式通過類加載機制來保證初始化的時候隻有一個Instance實例。它與餓漢式的不同就在于餓漢式隻要Singleton類被裝載了,那麼instance就會被實例化,也就是說沒有達到懶加載的效果。而采用靜态内部類的方式就是說Singleton被加載了,instance也不一定會被初始化,因為SingletonHolder類沒有被主動的使用,隻有調用了getInstance方法的時候SingletonHolder類才對instance進行了實例化。所以這種方式實現了對Instance對象的懶加載操作,也就是說在使用的時候在進行初始化。
懶漢式 下面我們來展示一種懶加載的單例設計模式,懶漢式,代碼如下。
public class Singleton { //定義實例 private static Singleton instance; //私有構造方法 private Singleton(){} //對外提供獲取實例的靜态方法 public static Singleton getInstance() { //在對象被使用的時候才實例化 if (instance == null) { instance = new Singleton(); } return instance; } }
上面的這種實現方式被稱為是懶漢式單例模式,所以為的懶漢式就是不會提前将實例創建出來,而是在第一次使用的時候,通過getInstance方法來創建一個實例對象。但是這種方式會出現線程安全問題,在多線程的情況下如果第一個線程調用了getInstance方法,第二個線程繼續調用getInstance方法這樣就會導緻兩個線程創建兩個Instance實例對象。這樣其實就不是我們想要的那種單例模式了。
線程安全的懶漢式實現 既然我們說上面這種實現方式是線程不安全的,那麼下面我們就來看一種線程安全的設計模式。
public class synchronizedSingleton { //定義實例 private static SynchronizedSingleton instance; //私有構造方法 private SynchronizedSingleton(){} //對外提供獲取實例的靜态方法,對該方法加鎖 public static synchronized SynchronizedSingleton getInstance() { //在對象被使用的時候才實例化 if (instance == null) { instance = new SynchronizedSingleton(); } return instance; } }
這種方式解決了多線程線程安全的問題,而且也有延遲加載的效果,但是,它的執行效率确實非常低的,因為對于單例模式來講,既然全局都需要同一個對象,那麼也就是對象全局隻會被創建一次,而後續的操作中則不需要再次創建這個對象,那麼在創建方法上添加Synchroinzed關鍵字來實現同步就不需要每次都進行同步操作了。這樣就出現了我們下面這種實現方式。
雙重校驗鎖 針對上面代碼中存在的問題,我們提出了如下的這種解決方案,既然上面的代碼中存在的問題是鎖的範圍過大,那麼我們就來減小鎖的範圍。代碼如下。
public class Singleton { private static Singleton singleton; private Singleton() { } public static Singleton getSingleton() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } }
上面這種寫法其實是線程安全的懶漢式寫法的改進操作,通過縮小同步代碼的範圍的方式來提升執行效率。但是這個代碼看上去并沒有什麼問題,實際上在很多的編程老師在講解這個地方的都會提到Java的内存模型,對雙重檢測鎖模式的影響。
關于這個知識點這裡我們不做展開的說明,有興趣的讀者可以自行查閱相關資料。
針對上面的問題,我們提出了如下的兩種解決方案。
使用Volatile關鍵字
public class VolatileSingleton { private static volatile VolatileSingleton singleton; private VolatileSingleton() { } public static VolatileSingleton getSingleton() { if (singleton == null) { synchronized (VolatileSingleton.class) { if (singleton == null) { singleton = new VolatileSingleton(); } } } return singleton; } }
使用final
class FinalWrapper { public final T value; public FinalWrapper(T value) { this.value = value; } } public class FinalSingleton { private FinalWrapperFinalSingleton helperWrapper = null; public FinalSingleton getHelper() { FinalWrapperFinalSingleton wrapper = helperWrapper; if (wrapper == null) { synchronized (this) { if (helperWrapper == null) { helperWrapper = new FinalWrapperFinalSingleton(new FinalSingleton()); } wrapper = helperWrapper; } } return wrapper.value; } }
枚舉式 在JDK1.5之前,單例模式一般隻有前面的幾種是實現方式,在JDK1.5之後,出現了基于枚舉類型的單例實現方式。
public enum Singleton { INSTANCE; Singleton() { } }
這種實現方式,不僅避免了多線程線程同步問題,而且還可以防止由于反序列化而創建新的對象等問題。但是實際開發過程中我們很少采用這種方式來實現單例設計模式。
總結 上面我們介紹了幾種實現單例設計模式的方法,并且介紹了各種方式的優缺點。希望大家多多關注,後續還會為大家帶來很多面試幹貨。
,
更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!