1. 程式人生 > >設計模式學習——單例模式

設計模式學習——單例模式

單例模式(一種物件建立型模式)

1.定義:保證一個類只有一個例項存在,同時提供能對該例項加以訪問的全域性訪問方法。
2.單例模式的要點:一是某個類只能有一個例項;二是它必須自行建立這個例項;三是它必須自行向整個系統提供這個例項。
*使用場景:確保某個類只有一個物件,避免產生多個物件消費過多的資源。
3.單例模式的版本:

  • 餓漢式:即當類載入進來時就立即例項化物件,但這種方式比較消耗計算機資源。
public class Singeton{
    private static final Singeton mSingeton = new Singeton();   //類載入時即初始化,保證物件的唯一性
    private Singeton(){   //私有化構造方法
     }
    public static Singeton getInstance(){    //對外提供獲取單例物件介面
    return mSingeton;
   }
}

* 懶漢式:要使用時才例項化,但多執行緒下存線上程安全問題。(在懶漢式的基礎上對函式進行同步的方式。)

public class Singeton{
    private static Singeton mSingeton ;   
    private Singeton(){   //私有化構造方法
     }
    public static synchronized Singeton getInstance(){    //加鎖保證執行緒安全
    if(mSingeton==null){
       mSingeton = new Singeton();
     }
   return mSingeton;
   }
}

4.如何實現懶漢式的執行緒安全?

加上synchronized即可

public static synchronized Singleton getInstance(){}
但這樣會降低整個訪問的速度,每次都要判斷,造成不必要的執行緒開銷,一般不這樣用。可以用雙重檢查加鎖改進。

5.雙重加鎖機制(高併發不推薦)

指的是:並不是每次進入getInstance方法都需要同步,而是先不同步,進入方法過後,先檢查例項是否存在,如果不存在才進入下面的同步塊,這是第一重檢查。進入同步塊後,再次檢查例項是否存在,如果不存在,就在同步的情況下建立一個例項。這是第二重檢查。

雙重加鎖機制的實現會使用一個關鍵字volatile,它的意思是:被volatile修飾的變數的值,將不會被本地執行緒快取,所有對該變數的讀寫都是直接操作共享記憶體,從而確保多個執行緒能正確的處理該變數。雖然會有點效能問題。

如果不加volatile關鍵字,在高併發情況下會有小概率出錯(java編譯器允許處理器亂序執行(先分配記憶體再初始化)。。。)

/**
 * 雙重檢查加鎖的單例模式
 */
public class Singleton {

    /**
     * 對儲存例項的變數新增volitile的修飾
     */
    private volatile static Singleton instance = null;  //如果不加volatile,在高併發情況會有小概率出錯
    private Singleton(){
    }

    public static Singleton getInstance(){
        //先檢查例項是否存在,如果不存在才進入下面的同步塊
        if(instance == null){
            //同步塊,執行緒安全的建立例項
            synchronized (Singleton.class) {
                //再次檢查例項是否存在,如果不存在才真正的建立例項
               if(instance==null) instance = new Singleton();
            }
        }
        return instance;
    }

}

6.靜態內部類單例模式(推薦使用)

public class Singleton {

    public static Singleton getInstance(){  //第一次呼叫時,載入SingletonHodler類
        return SingletonHolder.instance;
  }
    private Singleton(){  //私有化構造方法
    }  
     /* 類級的內部類,也就是靜態類的成員式內部類,該內部類的例項與外部類的例項
     * 沒有繫結關係,而且只有被呼叫時才會裝載,從而實現了延遲載入
     */
    private static class SingletonHolder{
        /**
         * 靜態初始化器,由JVM來保證執行緒安全
         */
        private static final Singleton instance = new Singleton();  //唯一性
    }
}

7.列舉單例

public enum SingletonEnum{
INSTANCE;
public void doSomething(){Systom.out.println("do sth")}
}
  • 預設列舉型別例項的建立是執行緒安全的,並且在任何情況下他都是一個單例!
  • 對於除列舉外的幾種實現在一種情況下會出現重新建立物件的情況,那就是反序列化,而列舉不存在這個問題,即使反序列化也不會生產新的物件。要杜絕這個問題必須加入如下方法:
private Object readResole() throws ObjectStramException{
    return sInstance;
}

8.不當使用單例模式導致記憶體洩漏
不正確使用單例模式是引起記憶體洩漏的一個常見問題,單例物件在初始化後將在JVM的整個生命週期中存在(以靜態變數的方式),如果單例物件持有外部的引用,那麼這個物件將不能被JVM正常回收,導致記憶體洩漏

class A{
public A(){
B.getInstance().setA(this);
}
....
}
//B類採用單例模式
class B{
private A a;  //B持有A的物件
private static B instance=new B();
public B(){}
public static B getInstance(){
return instance;
}
public void setA(A a){
this.a=a;
}
//getter...
} 

9.單例模式優缺點
優點:只需要呼叫一個單一的方法即可生成一個唯一的例項,有利於節約資源。
缺點:很難實現序列化,導致難以被持久化,難以通過網路傳輸;無法在繼承結構中使用。
10.android中單例模式無處不在,例如對服務的管理者ServiceManager就採用了單例模式。