單例模式的理解和示例


一、是什么

確保一個類只有一個實例,並提供一個全局訪問點

一般分類兩大類: 餓漢模式、懶漢模式

使用: 以前在線白鷺H5游戲時,因為有很多的場景類, 而每個場景類不需要創建很多遍, 所以使用單例模式

二、示例

1. 餓漢模式

/**
 * 餓漢模式, 線程安全, 但默認就創建實例, 占用空間
 */
public class Singleton1 {
    private static final Singleton1 instance = new Singleton1();

    private Singleton1() {
    }

    public static Singleton1 getIntance() {
        return instance;
    }
}

用空間換時間,默認就創建實例,所以沒有線程安全問題

2. 懶漢模式

/**
 * 懶漢模式, 線程不安全
 */
public class Singleton2 {
    private static Singleton2 instance = null;

    private Singleton2() {

    }

    public static Singleton2 getInstance() {
        if (instance == null) {
            instance = new Singleton2();
        }

        return instance;
    }
}

現在不安全在於,多個線程訪問getInstance()時,當一個線程已經初始化了,而另外一個線程並沒有感知,又重新創建了實例,這時候就不是單例

2.1 雙檢鎖 Double-check

/**
 * 懶漢模式--雙檢索
 */
public class SingletonDoubleCheck {
    private static SingletonDoubleCheck instance = null;

    private SingletonDoubleCheck() {

    }

    public static SingletonDoubleCheck getInstance() {
        if (instance == null) {
            synchronized (SingletonDoubleCheck.class) {
                if (instance == null) {
                    instance = new SingletonDoubleCheck();
                }
            }
        }

        return instance;
    }
}

為了在懶漢模式的基礎上,保證線程安全, 出現了雙檢鎖的設計,但是有出現了另一個問題。

在new SingletonDoubleCheck()時,是非原子性的,實際分為三步

  1. new 分配內存空間
  2. 初始化對象
  3. 將對象指向剛分配的內存空間

但JVM編譯器,為了性能考慮,可能重新排序2,3兩個, 變為:

  1. new 分配內存空間
  2. 將對象指向剛分配的內存空間
  3. 初始化對象

舉例說明

  線程1檢查到instance為空,獲取鎖,再次檢查instance為空,為instance分配內存空間,指向內存空間,這時線程2檢查到instance不為空,直接返回instance,但此時對象還沒有初始化完成

2.2 雙檢鎖 線程安全

/**
 * 使用volatile關鍵字的雙檢鎖
 */
public class SingletonDoubleCheck2 {
    /**
     * volatile關鍵字保證我在鎖instance時, 禁止JVM重排序
     */
    private volatile static SingletonDoubleCheck2 instance = null;

    private SingletonDoubleCheck2() {

    }

    public static SingletonDoubleCheck2 getInstance() {
        if (instance == null) {
            // 再次減少鎖的范圍, 只鎖instance變量
            synchronized (instance) {
                if (instance == null) {
                    instance = new SingletonDoubleCheck2();
                }
            }
        }

        return instance;
    }
}

使用volatile關鍵字來禁止JVM重排序

3.3 內部類實現

/**
 * 靜態內部類實現 -- 延遲加載
 *
 * 天生線程安全
 */
public class Singleton3 {

    /**
     * 私有化構造
     */
    private Singleton3() {
        System.out.println("初始化");
    }

    /**
     * 靜態內部類
     */
    private static class InnerObject{
        private static Singleton3 instance = new Singleton3();
    }

    public static Singleton3 getInstance() {
        return InnerObject.instance;
    }

    public static void main(String[] args) {
        getInstance();
    }
}

3.4 靜態代碼實現

/**
 * 懶漢模式 -- 靜態代碼塊實現
 */
public class Singleton4 {

    private static Singleton4 instance = null;

    static {
        instance = new Singleton4();
    }

    public static Singleton4 getInstance() {
        return instance;
    }
}

外部類加載時並不需要立即加載內部類,所以可以起到延時加載的目錄,

三、總結

單例模式是一個創建型的設計模式,能夠幫助開發者創建一個唯一的實例

使用的還是挺頻繁的


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM