單例模式(Singleton)概念,類別,代碼實現

1.什麼是單例模式?

單例模式是是一種常用的軟件設計模式,最簡單的設計模式之一。
單例模式是一種對象創建型模式,使用單例模式可以保證一個類只生成唯一的實例對象。即在整個程序空間中,該類只產生一個實例對象
應用場景:

  • 一些資源管理器常常設計成單例模式。

  • 在多個線程之間,比如servlet中,共享同一個資源或操作同一個對象。

  • 在整個程序空間使用全局變量,共享資源

  • 大規模系統中,爲了性能的考慮,需要節省對象的創建時間等等。

因爲單例模式保證一個類只產生一個實例,所以這些情況下,單例模式就派上用場了。

2.有哪幾種形式?

第一種形式:餓漢式

單例類
/**
 * 餓漢式 單例模式
 * 可以在多線程中保證,線程安全
 */
public class Person1 {
	private String name;
	public static Person1 person = new Person1();

	//將構造方法私有化,這樣在其他類中無法使用
	private Person1(){
	}
	
	//提供一個全局的靜態方法
	public static Person1 getPerson(){
		return person;
	}
	
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
	
}

測試類
public class Test {
	public static void main(String[] args) {
		Person1 p1 = Person1.getPerson();//通過靜態方法來創建對象實例
		p1.setName("李四");
		System.out.println(p1.getName());
		
		Person1 p2 = Person1.getPerson();
		System.out.println(p2 == p1);//true 說明p1和p2的地址相同,同一個對象
		System.out.println(p2.getName());//李四  p2並沒有賦值,但是name仍然是李四
		p2.setName("張三");
		System.out.println(p1.getName());
	}
}

餓漢式每次調用的時候不用做創建,直接返回已經創建好的實例。這樣雖然節省了時間,但是卻佔用了空間,實例本身爲static的,會一直在內存中帶着。因爲餓漢單例類在類的初始化時,已經自行實例化,所以線程安全。

第二種形式:懶漢式,也是常用的形式。

/**
 * 懶漢式
 * 只能在單線程中保證
 */
public class Person2 {
	private String name;
	private static Person2 person;

	//將構造方法私有化,這樣在其他類中無法使用
	private Person2(){
	}
	
	//提供一個全局的靜態方法
	public static Person2 getPerson(){
		if(person == null){
			person = new Person2(); 
		}
		return person;
	}
	
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
}

顧名思義,lazy loading(延遲加載,一說懶加載),在需要的時候才創建單例對象,而不是隨着軟件系統的運行或者當類被加載器加載的時候就創建。當單例類的創建或者單例對象的存在會消耗比較多的資源,常常採用lazy loading策略。這樣做的一個明顯好處是提高了軟件系統的效率,節約內存資源。

當多個線程併發時,可能會有幾個線程同時進入getPerson()的if語句中,然後分別創建不同的person對象,這就不能保證只有唯一一個對象,所以懶漢式線程不安全,只有單線程時是安全的。
通過使用同步方法(synchronized)修改getPerson()可以使程序符合單例模式要求:

public static synchronized Person2 getPerson(){
		if(person == null){
			person = new Person2(); 
		}
		return person;
}
	

當某個線程執行同步方法時(synchronized),其他線程不能執行該方法,即獨佔,從而保證只有一個實例。

但是這樣的話,會帶來新的問題。當某個線程在執行該方法時,其他線程就會等待,效率較低。改進的方法是:只需要把需要同步的語句同步,這樣就引出了第三種形式的單例模式。

第三種形式:雙重檢查

/**
 * 雙重檢查
 * 效率高
 */
public class Person4 {
	private String name;
	private static Person4 person;

	//將構造方法私有化,這樣在其他類中無法使用
	private Person4(){
	}
	
	//改進:只需要把需要同步的語句同步
	//但是可能有多個線程進入第一個if,所以需要第二次判斷
	public static Person4 getPerson(){
		if(person == null){
			synchronized (Person4.class) {
				if(person == null){
					person = new Person4(); 
				}
			}		
		}
		return person;
	}
	
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
}

這種經過改良的單例模式是線程安全的。

效率提高的原因?將同步內容移動到if內部,提高了執行的效率,不必每次獲取對象時都進行同步,只有第一次才同步,創建了以後就沒必要了。如果雙重if判斷,100的線程可以同時if判斷,理論消耗的時間只有一個if判斷的時間。

爲什麼需要第二次判斷?多個線程併發時,可能有多個線程進入第一個if,所以需要第二次判斷。

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