1. 程式人生 > >_lowbee瞎搞

_lowbee瞎搞

單例模式

一、什麼是單例模式

單例模式指的是在應用整個生命週期內只能存在一個例項。單例模式是一種被廣泛使用的設計模式。他有很多好處,能夠避免例項物件的重複建立,減少建立例項的系統開銷,節省記憶體。

二、單例模式和靜態類的區別

首先理解一下什麼是靜態類,靜態類就是一個類裡面都是靜態方法和靜態field,構造器被private修飾,因此不能被例項化。Math類就是一個靜態類。

知道了什麼是靜態類後,來說一下他們兩者之間的區別:

  1. 首先單例模式會提供給你一個全域性唯一的物件,靜態類只是提供給你很多靜態方法,這些方法不用建立物件,通過類就可以直接呼叫;
  2. 單例模式的靈活性更高,方法可以被override,因為靜態類都是靜態方法,所以不能被override;
  3. 如果是一個非常重的物件,單例模式可以懶載入,靜態類就無法做到;
  4. 那麼時候時候應該用靜態類,什麼時候應該用單例模式呢?首先如果你只是想使用一些工具方法,那麼最好用靜態類,靜態類比單例類更快,因為靜態的繫結是在編譯期進行的。如果你要維護狀態資訊,或者訪問資源時,應該選用單例模式。還可以這樣說,當你需要面向物件的能力時(比如繼承、多型)時,選用單例類,當你僅僅是提供一些方法時選用靜態類。

三、實現單例模式

這裡先放一下測試主程式

每個物件的hashCode相同,即每個物件相同;hashCode不同,即建立了多個物件,每個物件不同

class MyThread implements Runnable {

	@Override
	public void run() {
		Thread currentThread = Thread.currentThread();
		// 餓漢模式  執行緒安全
		//System.out.println(currentThread.getName() + SingletonHungry.getInstance().hashCode());
		// 懶漢模式  執行緒不安全
		//System.out.println(currentThread.getName() + SingletonLazy.getInstance().hashCode());
		// 懶漢模式  同步方法  執行緒安全
		//System.out.println(currentThread.getName() + SingletonLazySyncMethod.getInstance().hashCode());
		// 懶漢模式  同步程式碼塊  執行緒安全
		//System.out.println(currentThread.getName() + SingletonLazySyncBlock.getInstance().hashCode());
		// 懶漢模式  縮小同步程式碼塊  執行緒不安全
		//System.out.println(currentThread.getName() + SingletonLazySyncSmallBlock.getInstance().hashCode());
		// 懶漢模式  雙重鎖機制  執行緒安全
		//System.out.println(currentThread.getName() + SingletonLazyDoubleCheck.getInstance().hashCode());
	}
}

public class SingletonPatternTest {

	public static void main(String[] args) {
		// 開啟10個執行緒
		for (int i = 0; i < 10; i++) {
			MyThread myThread = new MyThread();
			// 開啟執行緒
			new Thread(myThread, "執行緒" + i + ":").start();
		}
	}
}

1、餓漢模式

所謂餓漢模式就是立即載入,一般情況下再呼叫getInstance方法之前就已經產生了例項,也就是在類載入的時候已經產生了。這種模式的缺點很明顯,就是佔用資源,當單例類很大的時候,其實我們是想使用的時候再產生例項。因此這種方式適合佔用資源少,在初始化的時候就會被用到的類。

// 餓漢模式  執行緒安全
public class SingletonHungry {

	private static SingletonHungry singletonHungry = new SingletonHungry();

	// 私有化構造方法
	private SingletonHungry() {}

	// 提供返回該物件的靜態方法
	public static SingletonHungry getInstance() {
		return singletonHungry;
	}
}

測試結果

2、懶漢模式1

懶漢模式就是延遲載入,也叫懶載入。在程式需要用到的時候再建立例項,這樣保證了記憶體不會被浪費。針對懶漢模式,這裡給出了幾種實現方式,但有些實現方式是執行緒不安全的,也就是說在多執行緒併發的環境下可能出現資源同步問題。

// 懶漢模式  執行緒不安全
public class SingletonLazy {

	private static SingletonLazy singletonLazy = null;

	private SingletonLazy() {}

	public static SingletonLazy getInstance() {
		if (null == singletonLazy) {
			try {
				// 模擬物件建立之前的準備工作
				Thread.sleep(1000);
				singletonLazy = new SingletonLazy();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		return singletonLazy;
	}
}

測試結果

3、懶漢模式2

因為上述懶漢模式1是執行緒不安全的,我們可以考慮用synchronized修飾getInstance方法進行同步實現執行緒安全

// 懶漢模式  執行緒安全
public class SingletonLazySyncMethod {

	private static SingletonLazySyncMethod singletonLazySyncMethod = null;

	private SingletonLazySyncMethod() {}
	
	public static synchronized SingletonLazySyncMethod getInstance() {
		if (null == singletonLazySyncMethod) {
			try {
				// 模擬物件建立之前的準備工作
				Thread.sleep(1000);
				singletonLazySyncMethod = new SingletonLazySyncMethod();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		return singletonLazySyncMethod;
	}
}

測試結果

這種方法雖然實現了執行緒安全,但是由於是同步執行的,下個執行緒想要取得物件,就必須要等到上一個執行緒釋放,才可以繼續執行,所以效率很低

4、懶漢模式3

上述懶漢模式2效率太低,我們可以將它稍微改進,不用同步方法實現執行緒安全,可以在方法內部採用同步程式碼塊實現執行緒安全,像這樣

// 懶漢模式  執行緒安全
public class SingletonLazySyncBlock {

	private static SingletonLazySyncBlock singletonLazySyncBlock  = null;
	
	private SingletonLazySyncBlock() {}
	
	public static SingletonLazySyncBlock getInstance() {
		synchronized (SingletonLazySyncBlock.class) {
			if (null == singletonLazySyncBlock) {
				try {
					// 模擬物件建立之前的準備工作
					Thread.sleep(1000);
					singletonLazySyncBlock = new SingletonLazySyncBlock();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			return singletonLazySyncBlock;
		}
	}
}

測試結果

這種方式雖然也實現了執行緒安全,但是效率還是很低

5、懶漢模式4

上述懶漢模式3使用同步程式碼塊實現了執行緒安全,但是效率還是較為低下。接下來。我們試著再次縮小同步程式碼塊的範圍

// 懶漢模式  執行緒不安全
public class SingletonLazySyncSmallBlock {

	private static SingletonLazySyncSmallBlock singletonLazySyncSmallBlock = null;
	
	private SingletonLazySyncSmallBlock() {}
	
	public static SingletonLazySyncSmallBlock getInstance() {
		if (null == singletonLazySyncSmallBlock) {
			try {
				// 模擬物件建立之前的準備工作
				Thread.sleep(1000);
				synchronized (SingletonLazySyncSmallBlock.class) {
					singletonLazySyncSmallBlock = new SingletonLazySyncSmallBlock();
				}
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		return singletonLazySyncSmallBlock;
	}
}

測試結果

上述測試結果,我們可以看到,很不辛失敗了,縮小了同步程式碼塊的範圍不能保證執行緒安全了。分析一下原因:我們假設有兩個執行緒A、B同時走進了if程式碼塊中,由於執行緒A比較牛批,首先搶到了鎖,就去建立物件了;而執行緒B只能等待,執行緒A建立完成物件之後,釋放鎖執行緒B拿到鎖也會去建立物件,這樣就建立了兩個物件,所以多執行緒環境下就不能保證單例了。

6、懶漢模式5

上述懶漢模式4,為什麼出現執行緒不安全,我們已經分析過了。接下來我們考慮如何去解決執行緒不安全。還拿上述執行緒A、B的例子來說,執行緒B之所以會再次建立物件,是因為執行緒B已經闖過了if條件的判斷,那麼我們此時考慮線上程B拿到鎖之前,再讓執行緒B去進行一次if判斷,也就是在synchronized程式碼塊之前再加一層if判斷,好的,問題解決。這樣,既縮小了同步程式碼塊的範圍,又保證了多執行緒環境下的執行緒安全。而且這種條件下,不像上述的懶漢模式2和懶漢模式3,懶漢模式2的同步方法是每次都要執行的,懶漢模式3的同步程式碼塊也是每次都要執行的,而這裡的懶漢模式5同步程式碼塊的執行次數與第一次同時闖過第一層if判斷的執行緒數相同,而後續執行緒過第一層if判斷時,在第一層if判斷就被攔截了。所以大大提高了程式的效率。這種方式就叫做雙重檢查鎖機制

// 懶漢模式  執行緒安全
public class SingletonLazyDoubleCheck {

	// volatile修飾
	private volatile static SingletonLazyDoubleCheck singletonLazyDoubleCheck = null;
	
	private SingletonLazyDoubleCheck() {}
	
	public static SingletonLazyDoubleCheck getInstance() {
		if (null == singletonLazyDoubleCheck) {
			// 模擬物件建立之前的準備工作
			try {
				Thread.sleep(1000);
				synchronized (SingletonLazyDoubleCheck.class) {
					if(null == singletonLazyDoubleCheck) {
						singletonLazyDoubleCheck = new SingletonLazyDoubleCheck();
					}
				}
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		return singletonLazyDoubleCheck;
	}
}

測試結果