單例模式(Singleton)
單例模式是在 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執行到:
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 核心知識整理&面試資料。