[Design Pattern] 單例模式 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;
    }
}
由這個簡單的單例模式可以看到,一樣的設計模式在不同的情況也是會有不同的變化。 設計模式不會是一段固定的程式碼,而是一種如何解決特定問題的概念。

留言

熱門文章