《Design Patterns》Singleton.積跬步系列

Singleton:單例模式

先代碼

package h.l.demo.singleton;

/**
 * 
 * @author: Is-Me-Hl
 * @date: 2020年3月7日
 * @Description: 單例模式測試類
 */
public class Singleton {

	public static void main(String[] args) {
		Instance instance = Instance.getInstance();
		Instance instance2 = Instance.getInstance();
		System.out.println("instance == instance2 result :" + (instance == instance2));

		Instance3 instance3 = Instance3.getInstance3();
		Instance3 instance4 = Instance3.getInstance3();
		System.out.println("instance3 == instance4 result :" + (instance3 == instance4));
	}
}

/**
 * 
 * @author: Is-Me-Hl
 * @date: 2020年3月3日
 * @Description: 單例模式:重要一點是構造函數私有,即無法通過new的方式創建對象實例,對外提供唯一的類方法供調用者訪問
 * 
 *               懶漢式,指的是當需要獲取該實例時纔去判斷實例是否存在,不存在再創建。
 *               餓漢式:指的是一開始我就給實例化好,需要的時候我就直接給你。
 * 
 *               兩者比較各有優缺點:餓漢式一開始就實例,佔內存,不如懶漢式中等需要時再創建對象的好。但懶漢式也存在問題,獲取實例對象的時候
 *               如果在多線程環境中
 *               ,可能存在線程安全,準確說可能多個線程同時都去獲取了實例,發現沒有該實例,又同時去創建了一個實例,這樣就不是單例
 *               的了,需要使用雙層鎖定的方式才能避免,這相對於餓漢式多了這些步驟。
 */
/**
 * 
 * @author: Is-Me-Hl
 * @date: 2020年3月4日
 * @Description: 單例模式-懶漢式-單線程環境
 */
class Instance {
	private static Instance instance;

	// 將構造函數私有,不允許通過new的方式創建對象
	private Instance() {
	}

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

/**
 * 
 * @author: Is-Me-Hl
 * @date: 2020年3月4日
 * @Description: 單例模式-懶漢式-多線程環境-雙重鎖定-volatile防止指令重排
 */
class Instance2 {
	private volatile static Instance2 instance2;

	// 創建一個鎖對象
	private static Object obj = new Object();

	// 將構造函數私有,不允許通過new的方式創建對象
	private Instance2() {
	}

	public static Instance2 getInstance2() {
		if (instance2 == null) {
			synchronized (obj) {
				if (instance2 == null) {
					instance2 = new Instance2();
				}
			}

		}
		return instance2;
	}
}

/**
 * 
 * @author: Is-Me-Hl
 * @date: 2020年3月4日
 * @Description: 單例模式-餓漢式
 */
class Instance3 {
	private static Instance3 instance3 = new Instance3();

	// 將構造函數私有,不允許通過new的方式創建對象
	private Instance3() {
	}

	public static Instance3 getInstance3() {
		return instance3;
	}
}

測試結果:
在這裏插入圖片描述

後分析

  • 個人建議:寫代碼是件幸福的事,So,do it

單例模式,定義:保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。實現方式:最好的辦法就是讓類自身負責保存它的唯一實例,這個類可以保證沒有其他實例可以創建,並且它可以提供一個訪問該實例的方法。
什麼時候使用單例模式?當類只允許創建一個實例的時候,即可使用該模式。對於上述demo,單例模式分單線程和多線程的區別,單線程中,無需使用鎖,好比一頓大餐就一個人喫,那需要那麼多的繁文縟節,排隊進餐等。多線程環境就需要上鎖,放置多個線程同時發現實例爲null,去調用實例化方法去同時創建新的實例。使用了volatile是保證實例化過程順序的一致,提供線程安全,防止當線程搶到鎖,剛給該實例分配內存還沒初始化時,其他線程發現該實例不爲null了,就拿該實例對象去執行操作,從而產生錯誤。

看似簡單的一段賦值語句:instance = new Singleton(); 其實JVM內部已經轉換爲多條指令:
memory = allocate(); //1:分配對象的內存空間
ctorInstance(memory); //2:初始化對象
instance = memory; //3:設置instance指向剛分配的內存地址
但是經過重排序後如下:
memory = allocate(); //1:分配對象的內存空間
instance = memory; //3:設置instance指向剛分配的內存地址,此時對象還沒被初始化
ctorInstance(memory); //2:初始化對象
可以看到指令重排之後,instance指向分配好的內存放在了前面,而這段內存的初始化被排在了後面,在線程A初始化完成這段內存之前,線程B雖然進不去同步代碼塊,但是在同步代碼塊之前的判斷就會發現instance不爲空,此時線程B獲得instance對象進行使用就可能發生錯誤。

其他例子:參考自《大話設計模式》有些類也需要計劃生育。


注:以上文章僅是個人總結,若有不當之處,望不吝賜教

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