1. 程式人生 > 實用技巧 >淺析_單例模式

淺析_單例模式

單例模式

單例模式(Singleton Pattern)是 Java 中最簡單的設計模式之一。這種型別的設計模式屬於建立型模式,它提供了一種建立物件的最佳方式。

這種模式涉及到一個單一的類,該類負責建立自己的物件,同時確保只有單個物件被建立。這個類提供了一種訪問其唯一的物件的方式,可以直接訪問,不需要例項化該類的物件。

注意:

  • 單例類只能有一個例項。
  • 單例類必須自己建立自己的唯一例項。
  • 單例類必須給所有其他物件提供這一例項。

1、餓漢式

特點:類一載入,就初始化了!因為類只會載入一次,故不存線上程安全問題。

public class Hungry {

    private static final Hungry HUNGRY = new Hungry();

    private Hungry(){}

    public static Hungry getInstance(){
        return HUNGRY;
    }
}

2、懶漢式(DCL)

特點:延遲載入,當需要呼叫的時候,再初始化!故多執行緒下存線上程安全問題。

這個比較經典,所以談談其中的細節:

(1)為什麼雙重校驗?
第一重校驗,是防止沒有必要的加鎖,避免影響 get 例項的效率;
第二重校驗,是防止加鎖的過程中,執行緒A突然阻塞,其他的執行緒搶先例項化了,而後面執行緒A被喚醒,導致物件被二次例項化。

(2)為什麼要加 volatile 關鍵字?
因為物件的初始化,大致可以分為三步:

  • 分配記憶體空間
  • 執行構造方法,例項化物件
  • 把棧幀裡定義的物件指向記憶體空間(棧中存放的是引用地址,只要指向了,就不為null 了)

那麼問題就來了:如果指令重排了,第二步初始化最後執行,然後陷入阻塞。那麼會有可能會導致,有執行緒獲取的單例物件,其實沒有初始化!

public class Lazy {

    private volatile static Lazy lazy;

    private Lazy(){	}

    // 雙重檢測鎖,懶漢式單例,DCL懶漢式
    public static Lazy getInstance(){
        if(lazy == null){ // 防止非必要加鎖
            synchronized (Lazy.class){
                if(lazy == null){ // 防止有執行緒在加鎖的過程中,對單例物件初始化了
                    lazy = new Lazy(); // 不是原子性操作
                }
            }
        }
        return lazy;
    }
}

3、靜態內部類

特點:和DCL懶漢式類似,但缺點是 外部無法傳遞引數 ,具體使用需自行取捨!執行緒安全是因為它也是利用了類載入的特性。

public class Holder {

    private Holder(){
    }

    public static Holder getInstance(){
        return InnerClass.HOLDER;
    }

    // 延遲載入
    public static class InnerClass{
        private static final Holder HOLDER = new Holder();
    }
}

4、列舉類

特點:最安全的單例,且 不會被反射破壞單例模式!(因為反射原始碼裡有對物件型別進行判斷,是否為列舉類)


package com.feng.single;

import java.lang.reflect.Constructor;

/**
 *  enum 本質也是一個Class 類
 *  列舉類實現單例:
 *      最安全,反射也沒有辦法破解它的單例。
 */
public enum EnumSingle {
    MY_INSTANCE;

    public EnumSingle getInstance(){
        return MY_INSTANCE;
    }

}

觀看原始碼得知:列舉類才是最安全的單例模式。因為連反射都沒辦法去構造它的物件,破壞它的單例!

5、總結

以上就是單例的幾種實現方式了,就各自的特點來說,DCL懶漢式列舉 是比較適合使用的。