Skip to content

Latest commit

 

History

History
129 lines (108 loc) · 4.57 KB

File metadata and controls

129 lines (108 loc) · 4.57 KB

單例模式 Singleton

####目的:保證一個類別只會產生一個物件,而且要提供存取該物件的統一方法

單例模式是一個簡單易懂的模式,下面的程式碼很簡單的就達到這樣的需求: 一開始我們就直接new出這個類別的實體物件,並且將constructor宣告為private, 這樣其他程式就無法再new出新物件,如此一來就能保證這個類別只會存在一個實體物件, 這種寫法因為一開始就已經建立物件,因此也稱為貪婪單例模式(Greed Singleton)。

public class SingletonGreed {
	// 一開始就建立物件,這樣只要一直回傳這個物件就是簡單的singleton
	private static SingletonGreed instance  = new SingletonGreed();
	
	// private constructor,這樣其他物件就沒辦法直接用new來取得新的實體
	private SingletonGreed(){}
	
	// 因為constructor已經private,所以需要另外提供方法讓其他程式調用這個類別
	public static SingletonGreed getInstance(){
		return instance;
	}
}

假如建立這個物件需要耗費很多資源,可是程式運行中不一定會需要它,我們希望只有在第一次getInstance被呼叫的時候才花費資源來建立物件,code就要修改一下

public class Singleton {
	private static Singleton instance;
		
	private Singleton(){
		// 這裡面跑很了多code,建立物件需要花費很多資源
	}
		
	public static Singleton getInstance(){
		// 第一次被呼叫的時候再建立物件
		if(instance == null){
			instance = new Singleton();
		} 
		return instance;
	}
}

以上程式看起來沒問題,不過如果是多執行緒的情況下呼叫getInstance,可能第一個執行緒跑到instance = new Singleton()時,將時間讓給第二個 執行緒,因此第二個執行緒也執行了instance = new Singleton(),造成同時new了兩個新的物件。

/**
 * 單例模式測試
 */
public class SingletonTest extends Thread {
    String myId;
    public SingletonTest(String id) {
        myId = id;
    }
    
    // 執行緒執行的時候就去呼叫Singleton.getInstance()
    public void run() {
    	Singleton singleton = Singleton.getInstance();
    	if(singleton != null){
    		// 用hashCode判斷前後兩次取到的Singleton物件是否為同一個
            System.out.println(myId+"產生 Singleton:" + singleton.hashCode());       		
    	}
    }
    
    public static void main(String[] argv) {
		/*
    	// 單執行緒的時候,s1與s2確實為同一個物件
    	Singleton s1  =  Singleton.getInstance();
    	Singleton s2  =  Singleton.getInstance();
    	System.out.println("s1:"+s1.hashCode() + " s2:" + s2.hashCode());
    	System.out.println();
    	*/
		
    	// 兩個執行緒同時執行
        Thread t1 = new SingletonTest("執行緒T1"); // 產生Thread物件
        Thread t2 = new SingletonTest("執行緒T2"); // 產生Thread物件
        t1.start(); // 開始執行t1.run()
        t2.start();
    }
}

為了解決這樣的問題,可以用synchronized修飾來解決這個問題,讓getInstance方法被調用的時候被lock住,就不會同時產生兩個物件。

public class Singleton {
	private static Singleton instance;
	
	
	private Singleton(){
		// 這裡面跑很了多code,建立物件需要花費很多資源
	}
	
	// 多執行緒時使用synchronized保證Singleton一定是單一的 
	public static synchronized Singleton getInstance(){
		if(instance == null){
			instance = new Singleton();
		} 
		return instance;
	}
}

上面這樣的寫法,synchronized整個方法會造成執行效能會變差,實際上需要lock住的只有創造物件的過程,也就是new Singleton這段程式碼而已, 因此可以將synchronized搬到getInstance方法內來加快程式的效能。

public class Singleton {
	private static Singleton instance;
	
	
	private Singleton(){
		// 這裡面跑很了多code,建立物件需要花費很多資源
	}
	
	// 多執行緒時,當物件需要被建立時才使用synchronized保證Singleton一定是單一的 ,增加程式校能
	public static Singleton getInstance(){
		if(instance == null){
			synchronized(Singleton.class){
				if(instance == null){
					instance = new Singleton();
				}	
			}
		} 
		return instance;
	}
}

由這個簡單的單例模式可以看到,一樣的設計模式在不同的情況也是會有不同的變化。 設計模式不會是一段固定的程式碼,而是一種如何解決特定問題的概念。