我學設計模式:單例(Singleton)模式

保證一個類僅有一個實例,并提供一個訪問它的全局訪問點。

要想控制一個類只被創建一個實例,那么首要的問題就是把創建實例的權限收回來,讓類自己來負責創建自己類的實例,然后由這個類來提供外部可以訪問這個類實例的方法,這就是單例模式的實現方法。

單例模式的結構和實現

單例模式的結構圖
單例模式的結構圖

Singleton:負責創建Singleton類自己的唯一實例,并提供一個getInstance的方法,來外部來訪問這個類的唯一實例。

  1. 私有化構造方法
  2. 提供靜態的獲取實例的方法
  3. 定義存儲實例的屬性,因為要在靜態方法中使用,因此要加上static修飾
  4. 實現控制實例的創建

示例代碼

懶漢模式

所謂懶漢模式,既然懶,那么在創建對象實例時就不要著急,在馬上要使用對象實例時才會創建,在裝載對象時不會創建對象實例。

package com.liuhao.designpattern.singleton;

/**
 * 單例模式懶漢模式
 * 
 * @author liuhao
 * 
 *         2015年7月17日
 */
public class SingletonLazy {
    // 4. 定義一個變量來存儲創建好的類實例
    // 5. 因為要在靜態方法中使用,因此要加上static修飾
    private static SingletonLazy instance = null;

    // 1. 構造方法私有化,好在內部控制創建實例的數目
    private SingletonLazy() {
    }

    // 2. 定義一個方法為客戶端提供類實例
    // 3. 這個方法需要定義成類方法,也就是
    public static SingletonLazy getInstance() {
        // 6. 判斷存儲實例的變量是否有值
        if (instance == null) {
            instance = new SingletonLazy();
        }
        return instance;
    }

    private String singletonData;// 單例可以有自己的屬性

    /**
     * 獲取屬性的值
     * 
     * @return 屬性的值
     */
    public String getSingletonData() {
        return singletonData;
    }

    /**
     * 單例可以有自己的操作方法
     */
    public void singletonOperation() {

    }
}

餓漢模式

所謂餓漢模式,就是在創建對象實例時比較急,在裝載類的時候就會創建對象實例。

package com.liuhao.designpattern.singleton;

/**
 * 單例模式餓漢模式
 * 
 * @author liuhao
 * 
 *         2015年7月17日
 */
public class SingletonHungry {
    // 4. 直裝載類的時候就會創建對象實例,只創建一次
    private static SingletonHungry instance = new SingletonHungry();

    // 1. 構造方法私有化,好在內部控制創建實例的數目
    private SingletonHungry() {
    }

    // 2. 定義一個方法為客戶端提供類實例
    // 3. 這個方法需要定義成類方法,也就是
    public static SingletonHungry getInstance() {
        // 5. 直接使用以及創建好的實例
        return instance;
    }

    private String singletonData;// 單例可以有自己的屬性

    /**
     * 獲取屬性的值
     * 
     * @return 屬性的值
     */
    public String getSingletonData() {
        return singletonData;
    }

    /**
     * 單例可以有自己的操作方法
     */
    public void singletonOperation() {

    }
}

其他

單例模式的范圍

目前Java實現的單例是一個虛擬機范圍的,因為裝載類的功能是虛擬機的,所以一個虛擬機在通過自己的ClassLoader裝載餓漢模式實現單例時就會創建一個類的實例。

因此如果一個虛擬機里面有很多個ClassLoader,而這些ClassLoader都裝載某個類的話,就會產生多個實例。

單例模式的調用順序

  • 懶漢模式
單例模式的調用順序
單例模式的調用順序
  • 餓漢模式
單例模式的調用順序
單例模式的調用順序

體現的一些思想

  • 延遲加載
    懶漢模式中,一開始沒有加載所需的資源或者數據,一直等到馬上就要使用了才加載,即所謂的“延遲加載”。

  • 緩存
    當某些資源或數據被頻繁的使用,而且它們是存儲在系統外部的(如數據庫、硬盤),那么每次操作都要從數據庫獲取,速度會很慢,操作性能問題。

一個簡單的方法就是把這些數據緩存到內存中,每次操作的時候先到內存里找,若有則直接使用;若果沒有再獲取它并設置到緩存中。

緩存是一種典型的空間換取時間的方案。

線程安全

  1. 不加同步的懶漢模式是線程不安全的。比如,有線程A、B同時調用getInstance方法,那就可能導致并發問題,如圖。


    懶漢模式線程沖突
    懶漢模式線程沖突
  2. 如何實現懶漢模式的線程安全

    1. 加上關鍵字synchronized,如下
    public static synchronized SingletonLazy getInstance() {}
    
    1. 雙重檢查加鎖。不是在每次進入getInstance時都需要同步,而是先不同步,進入方法后,先檢查實例是否存在。如果不存在才進入下面的同步塊,這是第一重檢查。進入同步塊后,再次檢查實例是否存在,如果不存在,就在同步的情況下創建一個實例,這是第二重檢查。
      雙重檢查加鎖機制的實現需要使用volatile關鍵字,被它修飾的變量的值將不會被本地線程緩存,所有對該變量的讀寫都是直接操作共享內存,從而確保多個線程能夠正確的處理該變量。
      具體實現:
    public static SingletonVolatile getInstance() {
        // 檢查實例是否存在,不存在則進入到同步塊
        if (instance == null) {
            // 同步塊,線程安全地創建實例
            synchronized (SingletonVolatile.class) {
                // 再次檢查實例是否存在,不存在則真正的創建實例
                if (instance == null) {
                    instance = new SingletonVolatile();
                }
            }
        }
        return instance;
    }
    

    這種實現方式既可以實現線程安全,同時也不會對性能造成太大的影響。

  3. 餓漢模式是線程安全的。


如果覺得有用,歡迎關注我的微信,有問題可以直接交流:

你的關注是對我最大的鼓勵!
你的關注是對我最大的鼓勵!
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容