1. 單例模式 什麼是單例模式?簡言之就是確保定義為單例模式的類在程式中有且只有一個實例。單例模式的特點: 1. 只有一個實例 (只能有一個對象被創建) 2. 自我實例化(類構造器私有) 3. 對外提供獲取實例的靜態方法 2.單例模式的實現 常見的單例模式實現方式有五種: 2.1. 懶漢式 懶漢式( ...
1. 單例模式
什麼是單例模式?簡言之就是確保定義為單例模式的類在程式中有且只有一個實例。單例模式的特點:
只有一個實例 (只能有一個對象被創建)
自我實例化(類構造器私有)
對外提供獲取實例的靜態方法
2.單例模式的實現
常見的單例模式實現方式有五種:
2.1. 懶漢式
懶漢式(一般也稱之為 飽漢式),具體代碼實現如下:
public class Singleton {
/**
* 自我實例化
*/
private static Singleton singleton;
/**
* 構造方法私有
*/
private Singleton() {
System.out.println("創建單例實例...");
}
/**
* 對外提供獲取實例的靜態方法
*/
public static Singleton getInstance() {
if (null == singleton) {
singleton = new Singleton();
}
return singleton;
}
}
從代碼實現中可以看到,實例並不是在一開始就是初始化的,而是在調用 getInstance()方法後才會產生單例,這種模式延遲初始化實例,但它並非是線程安全的。
public class SingleTonTest {
/**
* 多線程模式下測試懶漢模式是否線程安全
*
* @param args
*/
public static void main(String[] args) {
/**
* 這裡我圖方便,直接用Executors創建線程池
* 阿裡巴巴開發手冊是不推薦這麼做的
*/
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 0; i < 20; i++) {
executorService.execute(() -> System.out.println(Thread.currentThread().getName() + "::" + Singleton.getInstance()));
}
}
}
測試結果截圖:
懶漢式是在運行時載入對象的,所以載入該單例類時會比較快,但是獲取對象會比較慢。且這樣做是線程不安全的,如果想要線程安全,可以在getInstance()方法加上synchronized 關鍵詞修飾,但這樣會讓我們付出慘重的效率代價。
2.2. 餓漢式
提前創建好實例對象,調用效率高,但無法延時載入,容易產生垃圾,線程安全。
public class Singleton {
/**
* 自我實例化
*/
private static Singleton singleton = new Singleton();
/**
* 構造方法私有
*/
private Singleton() {
System.out.println("創建單例實例...");
}
/**
* 對外提供獲取實例的靜態方法
*/
public static Singleton getInstance() {
return singleton;
}
}
2.3. 雙重檢查鎖模式
public class Singleton {
/**
* 自我實例化,volatile修飾,保證線程間可見
*/
private volatile static Singleton singleton;
/**
* 構造方法私有
*/
private Singleton() {
System.out.println("創建單例實例...");
}
/**
* 對外提供獲取實例的靜態方法
*/
public static Singleton getInstance() {
// 第一次檢查,避免不必要的實例
if (singleton == null) {
// 第二次檢查,同步,避免產生多線程的問題
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
由於singleton=new Singleton()
對象的創建在JVM中可能會進行重排序,在多線程訪問下存在風險,使用volatile
修飾signleton
實例變數,能禁止指令重排,使得對象在多線程間可見,能夠有效解決該問題。
雙重檢查鎖定失敗的問題並不歸咎於 JVM 中的實現 bug,而是歸咎於 Java 平臺記憶體模型。記憶體模型允許所謂的“無序寫入”,這也是這些習語失敗的一個主要原因
2.4. 靜態內部類模式
public class Singleton {
/**
* 構造方法私有
*/
private Singleton() {
System.out.println("創建單例實例...");
}
private static class SingletonInner {
private static Singleton instance = new Singleton();
}
private static Singleton getInstance() {
return SingletonInner.singleton;
}
}
這樣寫充分利用靜態內部類的特點——初始化操作和外部類是分開的,只有首次調用getInstance()方法時,虛擬機才載入內部類(SingletonInner.class)並初始化instance, 保證對象的唯一性。
2.5. 枚舉單例模式
public enum Singleton {
INSTANCE
}
感覺異常簡單,預設枚舉類創建的對象都是單例的,且支持多線程。
3.單例模式總結
- 單例模式優點在於:全局只會生成單個實例,所以能夠節省系統資源,減少性能開銷。然而也正是因為只有單個實例,導致該單例類職責過重,違背了“單一職責原則”,單例類也沒有抽象方法,會導致比較難以擴展。
- 以上所有單例模式中,推薦使用靜態內部類的實現,非常直觀,且保證線程安全。在《Effective Java》中推薦枚舉類,但太簡單了,導致代碼的可讀性比較差。
- 單例模式是創建型模式,反序列化時需要重寫readResovle()方法,以保證實例唯一。
文章首發於本人博客:www.developlee.top, 轉載請註明出處!
關註公眾號,後臺回覆666, 領取福利: