設計模式學習——單例模式
單例模式(一種物件建立型模式)
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就採用了單例模式。