單例模式那件小事,看了你不會後悔

  歡迎關注下文:單例模式不是一件小事,快回來看看。html

  單例模式是一種建立型模式,某個類採用單例模式,則在這個類被建立後,只可能產生一個實例供外部訪問,而且提供一個全局的訪問點。安全

  主要思想以下:多線程

  • 將構造方法私有化( 聲明爲 private ),這樣外界不能隨意 new 出新的實例對象;
  • 聲明一個私有的靜態的實例對象,供外界使用;
  • 提供一個公開的方法,讓外界得到該類的實例對象。

  具體實現代碼以下:併發

  代碼①ide

public class Singleton {
    /**
     * 構造方法私有化
     */
    private Singleton() {
    }

    /**
     * 定義一個私有的靜態的實例
     */
    private static Singleton sSingleton = new Singleton();

    /**
     * 提供靜態的方法給外界訪問
     * 
     * @return
     */
    public static Singleton getInstance() {
        return sSingleton;
    }
}

  上面代碼①即是傳說中的餓漢式單例模式。餓漢式有一個缺點是在類一加載的時候,就實例化,提早佔用了系統資源。爲此,咱們能夠稍微優化一下:測試

  代碼②優化

public class Singleton {
    private Singleton() {
    }
private static Singleton sSingleton; public static Singleton getInstance() { if (sSingleton == null) { sSingleton = new Singleton(); } return sSingleton; } }

  這樣便成了「懶人」的使用方式,在須要使用該類的時候,再實例化,解決了上面所說的缺點,稱爲「懶漢式」。不過,這樣存在線程安全問題,可能會形成重複建立對象,與單例模式的思想相悖。因此咱們須要改進該方法:spa

  代碼③線程

public class Singleton {
    private Singleton() {
    }

    private static Singleton sSingleton;

    public synchronized static Singleton getInstance() {
        if (sSingleton == null) {
            sSingleton = new Singleton();
        }
        return sSingleton;
    }
}

  OK,給方法加上同步,就能夠避免併發環境下的問題了。這就是傳說中的懶漢式單例模式。同步該方法後,能夠避免多個線程重複建立對象,由於每次只能有一個線程去訪問該方法,其餘線程必須等待,假設如今實例對象已經初始化了,不須要再建立了,上面的方式就顯得效率低了。因此,該方法能夠繼續作以下優化:設計

  代碼④

public class Singleton {
    private Singleton() {
    }

    /**
     * volatile is since JDK5
     */
    private static volatile Singleton sSingleton;

    public static Singleton getInstance() {
        if (sSingleton == null) {
            synchronized (Singleton.class) {
                // 未初始化,則初始instance變量
                if (sSingleton == null) {
                    sSingleton = new Singleton();
                }
            }
        }
        return sSingleton;
    }
}

  上面的代碼,再也不同步方法了,採用雙重判斷,同步代碼塊,這樣大大地提升了懶漢式單例模式的效率,特別要注意的是:聲明對象的時候,使用了一個關鍵字:volatile,該關鍵字是從JDK1.5以後新加入的,不加該關鍵字,編譯器可能會失去大量優化的機會或者可能會在編譯時出現一些不可預知的錯誤。

  此外,還有一種靜態內部類實現單例模式的方法,以下:

  代碼⑤

public class Singleton {

    private Singleton () {
    }

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

   該方法簡單明瞭,不須要同步,代碼也不是很複雜。上面代碼中靜態內部類 InnerClassSingleton 在 Singleton 類加載的時候並不會加載,下面修改代碼④,進行驗證:

public class Singleton {

    public Singleton () {
    }

    private static class InnerClassSingleton {
        static{
            System.out.println("-----InnerClassSingleton 已經加載了....");
        }
        private final static Singleton sSingleton = new Singleton();
    }

    public static Singleton getInstance() {
        System.out.println(InnerClassSingleton.sSingleton.hashCode());
        return InnerClassSingleton.sSingleton;
    }
}

  注意特地將 Singleton 的構造方法公開化了,再編寫一個測試類:

public class Test {
    public static void main(String[] args) {
        new Singleton();
        System.out.println(Singleton.class);
    }
}

  運行代碼,打印結果只有一行:

class com.examle.joy.Singleton

  這說明靜態內部類並無加載,再次修改測試類:

public class Test {
    public static void main(String[] args) {
        System.out.println(Singleton.getInstance());
        System.out.println(Singleton.getInstance());
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Singleton.getInstance());
            }
        }).start();
    }
}

  此次的運行結果以下:

-----InnerClassSingleton 已經加載了...
com.examle.joy.Singleton@203b4f0e
com.examle.joy.Singleton@203b4f0e
com.examle.joy.Singleton@203b4f0e

  上面的打印結果是符合咱們預期的。

  最後總結一下:代碼⑤是單例模式最佳的實現方法,在實際開發中,餓漢式代碼簡潔,容易理解,用的比較多,若是不涉及併發操做的話,也可使用懶漢式代碼②,設計到多線程併發問題,用懶漢式的話,須要使用代碼④,代碼⑤顯得很優雅,是我的比較推薦的一種方式。最後值得一提的是,全部的單例模式,都只能用在非反射場景中,由於利用反射,成員變量或者方法即使聲明爲 private,也能夠被外部訪問。

相關文章
相關標籤/搜索