1. 程式人生 > 其它 >主流原始碼管理工具Github

主流原始碼管理工具Github

單例模式(Singleton Pattern),顧名思義,就是被單例的物件只能有一個例項存在。單例模式的實現方式是,一個類能返回物件的一個引用(永遠是同一個)和一個獲得該唯一例項的方法(必須是靜態方法)。通過單例模式,我們可以保證系統中只有一個例項,從而在某些特定的場合下達到節約或者控制系統資源的目的。

單例模式類圖

1. 餓漢模式

最常見、最簡單的單例模式寫法之一。顧名思義,“餓漢模式” 就是很 “飢渴”,所以一上來就需要給它新建一個例項。但這種方法有一個明顯的缺點,那就是不管有沒有呼叫過獲得例項的方法(本例中為 getWife() ),每次都會新建一個例項。

// 餓漢模式
public class Wife {

    // 一開始就新建一個例項
    private static final Wife wife = new Wife();

    // 預設構造方法
    private Wife() {}

    // 獲得例項的方法
    public static Wife getWife() {
        return wife;
    }
}

2. 懶漢模式

最常見、最簡單的單例模式之二,跟 “餓漢模式” 是 “好基友”。再次顧名思義,“懶漢模式” 就是它很懶,一開始不新建例項,只有當它需要使用的時候,會先判斷例項是否為空,如果為空才會新建一個例項來使用。

// 懶漢模式
public class Wife {

    //一開始沒有新建例項
    private static Wife wife;

    private Wife() { }

    // 需要時再新建
    public static Wife getWife() {
        if (wife == null) {
            wife = new Wife();
        }
        return wife;
    }
}

3. 執行緒安全的懶漢模式

是不是感覺很簡單?但是上面的懶漢模式卻存在一個嚴重的問題。那就是如果有多個執行緒並行呼叫 getWife() 方法的時候,還是會建立多個例項,單例模式就失效了。

Bug 來了,改改改!

簡單,我們在基本的懶漢模式上,把它設為執行緒同步(synchronized)就好了。synchronized 的作用就是保證在同一時刻最多隻有一個執行緒執行,這樣就避免了多執行緒帶來的問題。關於 synchronized 關鍵字,你可以 點選這裡 瞭解更多。

// 懶漢模式(執行緒安全)
public class Wife {
    private static Wife wife;

    private Wife() { }

    // 添加了 synchronized 關鍵字
    public static synchronized Wife getWife() {
        if (wife == null) {
            wife = new Wife();
        }
        return wife;
    }
}

4. 雙重檢驗鎖(double check)

執行緒安全的懶漢模式解決了多執行緒的問題,看起來完美了。但是它的效率不高,每次呼叫獲得例項的方法 getWife() 時都要進行同步,但是多數情況下並不需要同步操作(例如我的 wife 例項並不為空可以直接使用的時候,就不需要給 getWife() 加同步方法,直接返回 wife 例項就可以了)。所以只需要在第一次新建例項物件的時候,使用同步方法。

不怕,程式猿總是有辦法的。於是,在前面的基礎上,又有了 “雙重檢驗鎖” 的方法。

// 雙重鎖的 getWife() 方法
public static Wife getWife() {

    // 第一個檢驗鎖,如果不為空直接返回例項物件,為空才進入下一步
    if (wife == null) {
        synchronized (Wife.class) {

            //第二個檢驗鎖,因為可能有多個執行緒進入到 if 語句內
            if (wife == null) {
                wife = new Wife();
            }
        }
    }
    return wife ;
}

你以為這終於圓滿了?NO...Too young, too naive! 主要問題在於 wife = new Wife() 這句程式碼,因為在 JVM(Java 虛擬機器)執行這句程式碼的時候,要做好幾件事情,而 JVM 為了優化程式碼,有可能造成做這幾件事情的執行順序是不固定的,從而造成錯誤。(為了不把問題更加複雜化,這裡沒有深入講解在 JVM 中具體是怎麼回事,有興趣的同學可以點選 這裡 自行了解下。)

這個時候,我們需要給例項加一個 volatile 關鍵字,它的作用就是防止編譯器自行優化程式碼。最後,我們的“雙重檢驗鎖”版本終於出爐了。

// 雙重檢驗鎖
public class Wife {
    private volatile static Wife wife;

    private Wife() { }

    public static Wife getWife() {
        if (wife == null) {
            synchronized(Wife.class) {
                if (wife == null) {
                    wife = new Wife();
                }
            }
        }

        return wife;
    }
}

5. 靜態內部類

上面的方法,修修補補,實在是太複雜了... 而且 volatile 關鍵字在某些老版本的 JDK 中無法正常工作。咱們得換一種方法,即 “靜態內部類”。這種方式,利用了 JVM 自身的機制來保證執行緒安全,因為 WifeHolder 類是私有的,除了 getWife() 之外沒有其它方式可以訪問例項物件,而且只有在呼叫 getWife() 時才會去真正建立例項物件。(這裡類似於 “懶漢模式”)

// 靜態內部類
public class Wife {
    private static class WifeHolder {
        private static final Wife wife = new Wife();
    }

    private Wife() { }

    public static Wife getWife() {
        return WifeHolder.wife;
    }
}

6.列舉

還不懂什麼是列舉的,先 點選這裡 補補課。

如下,程式碼簡直是簡單得不能再簡單了。我們可以通過 Wife.INSTANCE 來訪問例項物件,這比 getWife() 要簡單得多,而且建立列舉預設就是執行緒安全的,還可以防止反序列化帶來的問題。這麼優(niu)雅(bi)的方法,來自於新版 《Effective Java》 這本書。這種方式雖然不常用,但是最為推薦。

// 列舉
public enum Wife {
    INSTANCE;

    // 自定義的其他任意方法
    public void whateverMethod() { }
}

單例模式的應用

當你只需要一個例項物件的時候,就可以考慮使用單例模式。比如在資源共享的情況下,避免由於多個資源操作導致的效能或損耗等就可以使用單例模式。

參考連結--- Java Design Pattern: Singleton