《單例模式》之雙重檢查加鎖DCL(結合SQLiteOpenHelper實例)

FBI Warning:歡迎轉載,但請標明出處:http://blog.csdn.net/codezjx/article/details/8883599,未經本人同意請勿用於商業用途,感謝支持! 

       前言:有一些對象其實我們只需要一個,比如說:線程池、緩存、對話框、處理偏好設置和註冊表的對象、日誌對象,充當打印機、顯卡等設備的驅動程序的對象。事實上,這類對象只能有一個實例,如果創造出多個實例,就會導致許多問題產生。例如:程序的行爲異常、資源使用過量,or不一致的結果。Android應用程序開發中,我們也會用到單例。如最經典的:單例SQLiteOpenHelper數據庫操作類,專門處理數據庫之間的交流。



如何實現單例模式?

首先看看單例模式的定義:確保一個類只有一個實例,並提供一個全局訪問點。


一、經典的單例模式實現:

	/** 單例*/
	private static DBHelper instance = null;
	
	private DBHelper(Context context) {
		super(context, DATABASE_NAME, null, DATABASE_VERSION);
		// TODO Auto-generated constructor stub
	}
	public static DBHelper getInstance(Context context) {
		if(instance == null) {
			instance = new DBHelper(context);
		}
		return instance;
	}

注意事項:

1、利用一個靜態變量來記錄DBHelper唯一實例
2、構造器必須聲明爲私有的,只有DBHelper類內部纔可以調用構造器
3、getInstance()方法實例化對象,並返回這個實例
4、上面的例子用的是延遲實例化的方式創建,對資源敏感的對象特別重要。



二、多線程下的單例

(1)災難的開始

上面的經典單例實現適合於單線程程序。然而,當引入多線程時,就必須通過同步來保護 getInstance() 方法。如果不保護 getInstance() 方法,則可能返回 DBHelper對象的兩個不同的實例。假設2個線程併發調用 getInstance() 方法並且線程1進入if(instance == null) 後,線程2同時進入if(instance == null)並new了一個對象,此時將存在兩個不同的實例。

解決方法:

只要把getInstance() 變成同步(synchronized)方法,多線程的災難幾乎可以輕鬆地解決。代碼如下:

	public static synchronized DBHelper getInstance(Context context) {
		if(instance == null) {
			instance = new DBHelper(context);
		}
		return instance;
	}

(2)新的災難

爲了確保我們的程序能應對多線程模式並正常工作,但這又帶來了另外一個災難:同步getInstance() 方法將會極大的拖垮性能。因爲同步一個方法可能造成程序執行效率下降100倍。我們可以進行如下的折衷選擇:

解決方法:

1、若性能對應用程序不是很關鍵,那就忘了上面的性能損失,使用同步getInstance() 方法。若getInstance() 方法運行在很頻繁的地方,就要重新考慮了。
2、若資源不敏感,則使用“急切”方法創建實例,而不是延遲實例化的方法,既在靜態初始化器中創建單件,保證線程安全。
3、採用“雙重檢查加鎖(DCL)”,在getInstance() 中減少使用同步,下面將重點介紹。



(3)雙重檢查加鎖(DCL):完美的解決方案

定義:

        利用“雙重檢查加鎖”,將首先檢查實例是否已經創建了,如果尚未創建,才進行同步。這樣一來,只有第一次會同步,這就是我們想要的。既避免了多線程災難,又減少了性能損失。看看代碼:

/** 單例*/
private static DBHelper instance = null;
    
	private DBHelper(Context context) {
		super(context, DATABASE_NAME, null, DATABASE_VERSION);
		// TODO Auto-generated constructor stub
	}
	
	/** 採用雙重檢查加鎖實例化單件*/
	public static DBHelper getInstance(Context context) 
	{
		if(instance == null)   //第一次檢查
		{
			synchronized (DBHelper.class) 
			{
				if(instance == null) //第二次檢查
				{
					instance = new DBHelper(context);
				}
			}
		}
		return instance;
	}

爲什麼需要第二次檢查?

        首先我們來分析沒有第二次檢查的情況:當instance爲null時,兩個線程可以併發地進入if語句內部。然後,一個線程進入synchronized塊來初始化instance,而另一個線程則被阻斷。當第一個線程退出synchronized塊時,等待着的線程進入並創建另一個DBHelper對象。注意:當第二個線程進入 synchronized 塊時,它並沒有檢查 instance 是否非 null。因此我們需要對instance進行第二次非空檢查,這也是“雙重檢查加鎖”名稱的由來。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章