01-Java設計模式-單例模式

Singleton-單例模式

單例顧名思義就是單個實例,保證在內存中只有一個實例。

使用場景用在只需要有一個實例存在的時候,不想讓別人在 new 一個實例出來的時候。

現在單例的寫法玩的越來越花哨了,各種寫法,本次介紹一下常用幾種方式及優缺點。

1、餓漢式

特點: JVM保證線程安全

優點:寫法比較簡單,推薦使用

缺點:不管是否用到,類裝載時就完成實例化了

public class Singleton01 {
    /**
     * 餓漢式
     * 類加載到內存後,只實例化一個,JVM保證線程安全
     *
     * 這種寫法比較簡單,缺點就是不管是否用到,類裝載時就完成實例化了。
     */
    private final static Singleton01 singleton = new Singleton01();

    // 構造方法設置成private,別人也就不能 new 了
    private Singleton01(){};

    public static Singleton01 getInstance() {
        return singleton;
    }

    // 可以定義自己的業務方法...
    public void test() {
        System.out.println("----- test ------");
    }

    public static void main(String[] args) {
        Singleton01 s1 = Singleton01.getInstance();
        Singleton01 s2 = Singleton01.getInstance();
        // s1 == s2  返回 true
        System.out.println(s1 == s2);
    }


}

2、懶漢式

特點:在用的時候初始化,不用不初始化

優點:即特點吧

缺點:非線程安全

注:爲什麼是非線程安全的?

如果一個線程調用 getInstance() 判斷 singleton 爲 null , 在還沒有執行到 singleton = new Singleton02(); 這一行的時候。
另外一個線程也開始調用 getInstance() 這時候在判斷 singleton 也是爲 null。
這樣就導致兩個線程分別執行了一次 singleton = new Singleton02();
所以說這個 singleton 就不再是同一個實例了。

public class Singleton02 {
    /**
     * 懶漢式
     * 在用的時候在初始化,不用不初始化。
     * 但是 線程不安全, 爲什麼是線程不安全的呢?
     *
     * 如果一個線程調用 getInstance() 判斷 singleton 爲 null , 在還沒有執行到 singleton =         
          new Singleton02(); 這一行的時候。
     * 另外一個線程也開始調用 getInstance() 這時候在判斷 singleton 也是爲 null。
     * 這樣就導致兩個線程分別執行了一次 singleton = new Singleton02();
     * 所以說這個 singleton 就不再是同一個實例了。
     */
    private static Singleton02 singleton = null;

    private Singleton02(){}

    public static Singleton02 getInstance() {
        if(singleton == null) {
            singleton = new Singleton02();
        }
        return singleton;
    }

    // 可以定義自己的業務方法...
    public void test() {
        System.out.println("----- test ------");
    }

    public static void main(String[] args) {
        Singleton02 s1 = Singleton02.getInstance();
        Singleton02 s2 = Singleton02.getInstance();
        // s1 == s2  返回 true
        System.out.println(s1 == s2);
    }

}

3、懶漢式-線程安全

特點:線程安全

優點:即特點

缺點:通過 synchronized 來解決線程安全問題, 但犧牲的是效率

public class Singleton03 {
    /**
     * 懶漢式-線程安全
     * 可以通過 synchronized 來解決線程安全問題, 但犧牲的是效率。
     */

    private static Singleton03 singleton = null;

    private Singleton03(){}

    public static synchronized Singleton03 getInstance() {
        if(singleton == null) {
            singleton = new Singleton03();
        }
        return singleton;
    }

    // 可以定義自己的業務方法...
    public void test() {
        System.out.println("----- test ------");
    }

    public static void main(String[] args) {
        Singleton03 s1 = Singleton03.getInstance();
        Singleton03 s2 = Singleton03.getInstance();
        // s1 == s2  返回 true
        System.out.println(s1 == s2);
    }
}

4、懶漢式 - 線程安全 - 雙重檢查

特點:線程安全

優點:在加鎖的同時,減小同步代碼塊的方式來提高效率

缺點:還是會犧牲效率

public class Singleton04 {
    /**
     *  懶漢式 - 線程安全 - 雙重檢查
     *
     *  在加鎖的同時,減小同步代碼塊的方式來提高效率
     */
    private static Singleton04 singleton = null;

    private Singleton04(){}

    public static synchronized Singleton04 getInstance() {
        if(singleton == null) {
            synchronized(Singleton04.class) {
                if(singleton == null) {
                    singleton = new Singleton04();
                }
            }
        }
        return singleton;
    }

    // 可以定義自己的業務方法...
    public void test() {
        System.out.println("----- test ------");
    }

    public static void main(String[] args) {
        Singleton04 s1 = Singleton04.getInstance();
        Singleton04 s2 = Singleton04.getInstance();
        // s1 == s2  返回 true
        System.out.println(s1 == s2);
    }
}

5、餓漢式-靜態內部類

特點:線程安全

優點:在加載外部類時不會加載內部類,這樣可以實現懶加載

缺點:這算是比較完美的寫法了,暫時沒有什麼缺點吧

public class Singleton05 {
    /**
     * 靜態內部類
     *
     * 在加載外部類時不會加載內部類,這樣可以實現懶加載
     *
     * 比較完美的寫法
     */
    private Singleton05(){}

    private static class Singleton05Holder{
        private final static Singleton05 singleton = new Singleton05();
    }

    public static Singleton05 getInstance() {
        return Singleton05Holder.singleton;
    }

    // 可以定義自己的業務方法...
    public void test() {
        System.out.println("----- test ------");
    }

    public static void main(String[] args) {
        Singleton05 s1 = Singleton05.getInstance();
        Singleton05 s2 = Singleton05.getInstance();
        // s1 == s2  返回 true
        System.out.println(s1 == s2);
    }

}

6、枚舉

特點:好像是Java創始人之一提出的,在 Effective Java 書中有提到

優點:解決了線程安全問題,還可以防止反序列化

注:枚舉單例爲什麼不會被反序列化?

枚舉類沒有構造方法

public enum Singleton06 {
    /**
     * 堪稱最完美的寫法 在 Effective Java 書中有提到
     *
     * 解決了線程安全問題,還可以防止反序列化
     *
     * 枚舉單例不會被反序列化的原因是:枚舉類沒有構造方法。
     */
    singleton;

    // 可以定義自己的業務方法...
    public void test() {
        System.out.println("----- test ------");
    }

    public static void main(String[] args) {
        Singleton06 s1 = Singleton06.singleton;
        Singleton06 s2 = Singleton06.singleton;
        // s1 == s2  返回 true
        System.out.println(s1 == s2);
    }
}

--------------------------------------------------------------------------    END      -----------------------------------------------------------------------------

在工作中一般常用的還是第一種方式,簡單、安全,雖然有一點點小瑕疵,個人覺得還可以。

如有問題,歡迎交流!

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