單例模式是在 GOF
的23種設計模式裏較爲簡單的一種,下面引用百度百科介紹:面試
單例模式,是一種經常使用的軟件設計模式。在它的核心結構中只包含一個被稱爲單例的特殊類。經過單例模式能夠保證系統中,應用該模式的類一個類只有一個實例。即一個類只有一個對象實例設計模式
許多時候整個系統只須要擁有一個的全局對象,這樣有利於咱們協調系統總體的行爲。好比在某個服務器程序中,該服務器的配置信息存放在一個文件中,這些配置數據由一個單例對象統一讀取,而後服務進程中的其餘對象再經過這個單例對象獲取這些配置信息。這種方式簡化了在複雜環境下的配置管理。安全
在Java中,確保一個類只有一個對象實例能夠經過權限的修飾來實現。服務器
單例模式的餓漢模式指全局的單例實例在第一次被使用時構建。
具體實現:多線程
// 單例模式的餓漢模式實現 public class Singleton { private final static Singleton SINGLETON= new Singleton(); // Private constructor suppresses private Singleton() {} // default public constructor public static Singleton getInstance() { return SINGLETON; } }
在餓漢模式實現方式中,程序的主要特色是:併發
懶漢模式,也是最經常使用的形式,餓漢模式讓程序在初始化時候進行加載,有時爲了節約資源,咱們須要在須要的時候進行加載,這時候咱們能夠使用懶漢模式。
具體實現:網站
public class SingletonLayload { // 私有化自身類對象 private static SingletonLayload SINGLETON; // 私有化構造方法 private SingletonLayload() {} // 靜態方法獲取實例 public static SingletonLayload getInstance() { if(SINGLETON== null ) { SINGLETON= new SingletonLayload(); } return SINGLETON; } }
在多線程的環境中,簡單的單例模式將會出現問題,試想在上面的懶漢模式中,若是多線程併發執行getInstance()
,當線程A執行到:.net
INSTANCE = new SingletonLayload();
線程
卻尚未執行完畢時,線程B執行到if(INSTANCE == null )
,此時就沒法保證單例特性。
所以在多線程環境中,單例模式須要使用同步鎖確保實現真正的單例。
具體實現:設計
public class SingletonLayloadSyn { // 私有化自身類對象 private static SingletonLayloadSyn SINGLETON; // 私有化構造方法 private SingletonLayloadSyn() {} // 靜態方法獲取實例 public static synchronized SingletonLayloadSyn getInstance() { if(SINGLETON == null ) { SINGLETON = new SingletonLayloadSyn(); } return SINGLETON; } }
經過在getInstance()
方法上添加 synchronized
關鍵字能夠解決多線程帶來的問題。
使用上面的( 多線程下 - 懶漢模式 - 同步鎖)方式在解決多線程問題時雖然能夠達到確保線程安全的目的,可是使用了synchronized
關鍵字以後在須要屢次調用時,會讓代碼的執行效率大大下降。那麼有沒有在確保線程安全的同時又能夠兼顧效率的方法呢?
具體實現:
public class SingletonLayLoadSynDCL { // 私有化自身類對象 private static SingletonLayLoadSynDCL SINGLETON; // 私有化構造方法 private SingletonLayLoadSynDCL() { } public static SingletonLayLoadSynDCL getInstance() { if (SINGLETON == null) { synchronized(SingletonLayLoadSynDCL.class) { SINGLETON = new SingletonLayLoadSynDCL(); } } return SINGLETON; } }
使用 synchronized
確保線程安全,在SINGLETON 爲 null
時才進行建立實例,可是仍然不能 保證在實例未建立完成時候有新的線程執行到 if (SINGLETON == null)
;所以,仍然不夠安全。
修改 getInstance()
方法。
具體實現:
public class SingletonLayLoadSynDCL { // 私有化自身類對象 private static SingletonLayLoadSynDCL SINGLETON; // 私有化構造方法 private SingletonLayLoadSynDCL() { } // 使用雙重校驗鎖確保線程安全的同時兼顧執行效率 public static SingletonLayLoadSynDCL getInstance() { if (SINGLETON == null) { // 第一重檢查 synchronized (SingletonLayLoadSynDCL.class) { if (SINGLETON == null) { //第二重檢查 SINGLETON = new SingletonLayLoadSynDCL(); } } } return SINGLETON; } }
看似完美的雙檢查模式,在理論上是沒有問題的。可是在實際的狀況裏,有可能發生在沒有構造完畢的狀況下SINGLETON 引用已經不是 NULL 的狀況,這時候若是有其餘線程執行到if (SINGLETON == null) { // 第一重檢查
則會獲取到一個不正確的 SINGLETON 引用。這是因爲JVM
的無序寫入引發的。
幸虧,在 JDK1.5
以後,提供了volatile
關鍵字,用於確保被修飾的變量的讀寫不容許被控制。所以修改上面具體實現爲:
/** * <p> * 使用雙重校驗鎖以及volatile關鍵字確保線程安全的同時兼顧執行效率 * @author niujinpeng */ public class SingletonLayLoadSynDCL { // 私有化自身類對象 // private static SingletonLayLoadSynDCL SINGLETON; private volatile static SingletonLayLoadSynDCL SINGLETON; // 私有化構造方法 private SingletonLayLoadSynDCL() {} // 使用雙重校驗鎖確保線程安全的同時兼顧執行效率 public static SingletonLayLoadSynDCL getInstance() { if (SINGLETON == null) { synchronized (SingletonLayLoadSynDCL.class) { if (SINGLETON == null) { SINGLETON = new SingletonLayLoadSynDCL(); } } } return SINGLETON; } }
除了使用上面的懶漢模式實現方式以外,在解決多線程問題中,《Effective Java》的做者給出了另一種保證線程安全且兼顧效率的方式,利用了靜態內部類以及類加載特性實現。靜態內部類只有在調用時纔會加載,而靜態屬性隨着類的加載而加載,類的加載初始化只會有一次。所以保證了獲取實例的惟一性。
具體實現:
package cn.snowflow.pattern.singleton; /** * <p> * 利用靜態內部類實現線程安全且兼顧效率的單例模式 * @author niujinpeng */ public class SingletonLayloadSynSafe { //靜態內部類 public static class SingletonHolder{ static final SingletonLayloadSynSafe INSTANCE = new SingletonLayloadSynSafe(); } // 私有化構造方法 private SingletonLayloadSynSafe() {} // 公有方法獲取實例 public static SingletonLayloadSynSafe getInstance() { return SingletonHolder.INSTANCE; } }
若是使用單例模式-餓漢模式,推薦【單例模式 - 餓漢模式】
。
若是使用單例模式-懶漢模式,推薦【單例模式 - 懶漢模式 - 內部類 】
。
<完>
我的網站:https://www.codingme.net
若是你喜歡這篇文章,能夠關注公衆號,一塊兒成長。
關注公衆號回覆資源能夠沒有套路的獲取全網最火的的 Java 核心知識整理&面試資料。