1. 程式人生 > >設計模式之單例模式

設計模式之單例模式

ins 一次 初始 之間 懶加載 sin jdk1.5 代碼 tin

一、概念

  Java中單例模式是一種非常常見的設計模式,單例模式確保某個類只有一個實例,而且自行實例化並向整個系統提供這個實例。

二、特點

  1、單例類只有一個實例。

  2、單例類必須自己創建自己的唯一實例。

  3、單例類必須給所有其它對象提供這一實例。

三、種類

  1、懶漢式單例:懶漢式就是不在系統加載時就創建類的單例,而是在第一次使用實例的時候再創建。

public class Singleton {
	
     //定義一個私有類變量來存放單例,私有的目的是指外部無法直接獲取這個變量,而要使用提供的公共方法來獲取 private static Singleton instance = null; // 定義私有構造方法,表示只在類內部使用,亦指單例的實例只能在單例類內部創建(防止類外部通過 new Singleton()去實例化) private Singleton() { } //定義一個公共的公開的方法來返回該類的實例,由於是懶漢式,需要在第一次使用時生成實例,所以為了線程安全,使用synchronized關鍵字來確保只會生成單例 public static synchronized Singleton getInstance() { if(null == instance) { instance = new Singleton(); } return instance; } }

  線程A希望使用SingletonClass,調用getInstance()方法。因為是第一次調用,線程A就發現instance是null的,於是它開始創建實例,就在這個時候,CPU發生時間片切換,線程B開始執行,它要使用SingletonClass,調用getInstance()方法,同樣檢測到instance是null(註意:這是在線程A檢測完之後切換的),也就是說線程A並沒有來得及創建對象——因此線程B開始創建。線程B創建完成後,切換到A繼續執行,因為線程A已經檢測過了,所以線程A不會再檢測一遍,它會直接創建對象。這樣,線程A和B各自擁有一個SingletonClass的對象,因此單例對象創建失敗,解決辦法就是使用synchronized關鍵字加鎖,保證線程安全。

  2、餓漢式單例:在加載類的時候就會創建類的單例,並保存在類中。這種方式比較常用,但容易產生垃圾對象,效率比較低,線程安全

public class Singleton {

	// 此處定義類變量實例並直接實例化,在類加載的時候就完成了實例化並保存在類中
	private static final Singleton instance = new Singleton();
	
	// 定義一個私有的無參構造方法,防止該單例類被在外部實例化
	private Singleton() {
	
	}
	
	// 靜態方法返回該類的實例
	public static Singleton getInstance() {
		return instance;
	}
}

  3、雙重加鎖機制單例:這種方式采用雙鎖機制,線程安全且在多線程下能保持高性能

public class Singleton {
	
	private volatile static Singleton instance = null;
	
	private Singleton() {
		
	}
	
	public static Singleton getInstance() {
		if(null == instance) {
			synchronized(Singleton.class) {
				if(null == instance) {
					instance = new Singleton();
				}
			}
		}
		return instance;
	}
	
}

  這裏的雙重指的的雙重判斷,而加鎖單指那個synchronized,為什麽要進行雙重判斷,其實很簡單,第一重判斷,如果單例已經存在,那麽就不再需要進行同步操作,而是直接返回這個實例,如果沒有創建,才會進入同步塊,同步塊的目的與之前相同,目的是為了防止有兩個調用同時進行時,導致生成多個實例,有了同步塊,每次只能有一個線程調用能訪問同步塊內容,當第一個搶到鎖的調用獲取了實例之後,這個實例就會被創建,之後的所有調用都不會進入同步塊,直接在第一重判斷就返回了單例。  

  如果某個線程A發現instance還沒初始化,那麽就進入同步塊初始化instance,如果在這期間有其他線程B進入,那麽線程B就會等待進入同步塊,等待A 線程退出同步塊,instance已經被初始化了,線程B進入同步塊後發現instance不為null,即退出同步塊,不再進行初始化instance。 這樣既實現了線程安全,又兼顧了效率,確實是很聰明的編碼方式。但是問題來了,由於Java指令重排序的存在,會導致其他線程可能會得到未完全初始化的對象,所以JDK1.5版本之後擴展了volitile關鍵字,可以保證上述代碼的正確性。

  volatile關鍵字的含義是:被其所修飾的變量的值不會被本地線程緩存,所有對該變量的讀寫都是直接操作共享內存來實現,從而確保多個線程能正確的處理該變量。Volatile類型的變量不會被緩存在寄存器中(寄存器中的數據只有當前線程可以訪問),或者其他對CPU不可見的地方,每次都需要從主存中讀取對應的數據,這保證每次對變量的修改,其他線程也是可見的,而不是僅僅修改自己線程的局部變量

  4、靜態內部類單例:使用類級內部類結合多線程默認同步鎖,同時實現延遲加載和線程安全。

public class Singleton {
	
	private Singleton() {

        }
	
	private static class SingletonHolder {
		private static final Singleton INSTANCE = new Singleton();
	}
	
	public static final Singleton getInstance() {
		return SingletonHolder.INSTANCE;
	}

}

  所謂類級內部類,就是靜態內部類,這種內部類與其外部類之間並沒有從屬關系,加載外部類的時候,並不會同時加載其靜態內部類,只有在發生調用的時候才會進行加載,加載的時候就會創建單例實例並返回,有效實現了懶加載(延遲加載),至於同步問題,我們采用和餓漢式同樣的靜態初始化器的方式,借助JVM來實現線程安全。

四、使用

  在Spring中創建的Bean實例默認都是單例模式存在的。

設計模式之單例模式