Java設計模式(二)——————【創建型模式】設計模式之單例模式

源碼地址:https://github.com/877148107/java-design-pattern 


目錄

單例設計模式介紹

單例設計模式八種方式

1、餓漢式(靜態常量)

1)代碼案例

2)優點和缺點

2、餓漢式(靜態代碼塊)

1)代碼案例

2)優點和缺點

3、 懶漢式(線程不安全)

1)代碼案例

2)優點和缺點

4、懶漢式(線程安全,同步方法)

1)代碼案例

2)優點和缺點

5、懶漢式(線程安全,同步代碼塊)

1)代碼案例

2)優點和缺點

6、雙重檢查

1)代碼案例

2)優點和缺點

7、靜態內部類

1)代碼案例

2)優點和缺點

8、枚舉

1)代碼案例

2)優點和缺點

JDK中的單例模式

總結


  • 單例設計模式介紹

所謂類的單例設計模式,就是採取一定的方法保證在整個的軟件系統中,對某個類只能存在一個對象實例,並且該類只提供一個取得其對象實例的方法(靜態方法)。

比如Hibernate的SessionFactory,它充當數據存儲源的代理,並負責創建Session 對象。SessionFactory並不是輕量級的,一般情況下,一個項目通常只需要一個SessionFactory就夠,這是就會使用到單例模式。

  • 單例設計模式八種方式

1、餓漢式(靜態常量)

1)代碼案例

1、構造器私有化 (防止 new )

2、類的內部創建對象

3、向外暴露一個靜態的公共方法。getInstance

public class SingleTon01 {

    public static void main(String[] args) {
        //測試
        Singleton instance = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance == instance2);
        System.out.println("instance.hashCode=" + instance.hashCode());
        System.out.println("instance2.hashCode=" + instance2.hashCode());
    }
}

class Singleton{

    /**
     * 私有化構造器,防止外部new
     */
    private Singleton(){

    }

    /**
     * 本類內部創建對象實例
     */
    private final static Singleton instance = new Singleton();

    /**
     * 提供一個公有方法返回實例對象
     */
    public static Singleton getInstance(){
        return instance;
    }
}

2)優點和缺點

1、優點:這種寫法比較簡單,就是在類裝載的時候就完成實例化。避免了線程同步問題。

2、缺點:在類裝載的時候就完成實例化,沒有達到Lazy Loading的效果。如果從始至終從未使用過這個實例,則會造成內存的浪費

3、這種方式基於classloder機制避免了多線程的同步問題,不過,instance在類裝載時就實例化,在單例模式中大多數都是調用getInstance方法, 但是導致類裝載 的原因有很多種,因此不能確定有其他的方式(或者其他的靜態方法)導致類裝載,這時候初始化instance就沒有達到lazy loading的效果

4、結論:這種單例模式可用,可能造成內存浪費

2、餓漢式(靜態代碼塊)

1)代碼案例

1、在靜態代碼塊執行時,創建實例對象

public class SingleTon02 {

    public static void main(String[] args) {
        //測試
        Singleton instance = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance == instance2);
        System.out.println("instance.hashCode=" + instance.hashCode());
        System.out.println("instance2.hashCode=" + instance2.hashCode());
    }
}

class Singleton{

    /**
     * 私有化構造器,防止外部new
     */
    private Singleton(){

    }

    private static Singleton instance;

    static {
        instance = new Singleton();
    }

    /**
     * 提供一個公有方法返回實例對象
     */
    public static Singleton getInstance(){
        return instance;
    }
}

2)優點和缺點

1) 這種方式和上面的方式其實類似,只不過將類實例化的過程放在了靜態代碼塊中,也是在類裝載的時候,就執行靜態代碼塊中的代碼,初始化類的實例。優缺點和上面是一樣的。

2) 結論:這種單例模式可用,但是可能造成內存浪費

3、 懶漢式(線程不安全)

1)代碼案例

public class SingleTon03 {

    public static void main(String[] args) {
        Singleton instance = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance == instance2);
        System.out.println("instance.hashCode=" + instance.hashCode());
        System.out.println("instance2.hashCode=" + instance2.hashCode());
    }
}

class Singleton{

    private static Singleton instance;

    private Singleton(){

    }

    /**
     * 靜態公有方法,當使用到該方法的時候纔去創建
     */
    public static Singleton getInstance(){
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

2)優點和缺點

1、 起到了Lazy Loading的效果,但是隻能在單線程下使用。

2)、如果在多線程下,一個線程進入了if (singleton == null)判斷語句塊,還未來得及往下執行,另一個線程也通過了這個判斷語句,這時便會產生多個實例。所以在多線程環境下不可使用這種方式

3、 結論:在實際開發中,不要使用這種方式

4、懶漢式(線程安全,同步方法)

1)代碼案例

public class SingleTon04 {

    public static void main(String[] args) {
        Singleton instance = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance == instance2);
        System.out.println("instance.hashCode=" + instance.hashCode());
        System.out.println("instance2.hashCode=" + instance2.hashCode());
    }
}

class Singleton{

    private static Singleton instance;

    private Singleton(){

    }

    /**
     * 靜態公有方法,當使用到該方法的時候纔去創建
     * synchronized 解決線程不安全問題
     */
    public synchronized static Singleton getInstance(){
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

2)優點和缺點

1、解決了線程不安全問題

2、效率太低了,每個線程在想獲得類的實例時候,執行getInstance()方法都要進行 同步。而其實這個方法只執行一次實例化代碼就夠了,後面的想獲得該類實例, 直接return就行了。方法進行同步效率太低

3、 結論:在實際開發中,不推薦使用這種方式

5、懶漢式(線程安全,同步代碼塊)

1)代碼案例

public class SingleTon05 {

    public static void main(String[] args) {
        Singleton instance = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance == instance2);
        System.out.println("instance.hashCode=" + instance.hashCode());
        System.out.println("instance2.hashCode=" + instance2.hashCode());
    }
}

class Singleton{

    private static Singleton instance;

    private Singleton(){

    }

    /**
     * 靜態公有方法,當使用到該方法的時候纔去創建
     */
    public static Singleton getInstance(){
        if (instance == null) {
            synchronized(Singleton.class){
                instance = new Singleton();
            }
        }
        return instance;
    }
}

2)優點和缺點

1、這種方式,本意是想對第四種實現方式的改進,因爲前面同步方法效率太低, 改爲同步產生實例化的的代碼塊

2、但是這種同步並不能起到線程同步的作用。跟第3種實現方式遇到的情形一致,假如一個線程進入了if (singleton == null)判斷語句塊,還未來得及往下執行,另一個線程也通過了這個判斷語句,這時便會產生多個實例

3、結論:在實際開發中,不能使用這種方式

6、雙重檢查

1)代碼案例

public class SingleTon05 {

    public static void main(String[] args) {
        Singleton instance = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance == instance2);
        System.out.println("instance.hashCode=" + instance.hashCode());
        System.out.println("instance2.hashCode=" + instance2.hashCode());
    }
}

class Singleton{

    private static Singleton instance;

    private Singleton(){

    }

    /**
     * 靜態公有方法,當使用到該方法的時候纔去創建
     */
    public static Singleton getInstance(){
        if (instance == null) {
            synchronized(Singleton.class){
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

2)優點和缺點

1) Double-Check概念是多線程開發中常使用到的,如代碼中所示,我們進行了兩次if (singleton == null)檢查,這樣就可以保證線程安全了。

2) 這樣,實例化代碼只用執行一次,後面再次訪問時,判斷if (singleton == null),直接return實例化對象,也避免的反覆進行方法同步.

3) 線程安全;延遲加載;效率較高

4) 結論:在實際開發中,推薦使用這種單例設計模式

7、靜態內部類

1)代碼案例

public class SingleTon07 {

    public static void main(String[] args) {
        Singleton instance = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance == instance2);
        System.out.println("instance.hashCode=" + instance.hashCode());
        System.out.println("instance2.hashCode=" + instance2.hashCode());
    }
}

class Singleton{


    private Singleton(){

    }

    private static class SingletonInstance{
        private static final Singleton INSTANCE = new Singleton();
    }

    /**
     * 靜態公有方法,當使用到該方法的時候纔去創建
     */
    public static Singleton getInstance(){
        return SingletonInstance.INSTANCE;
    }
}

2)優點和缺點

1、這種方式採用了類裝載的機制來保證初始化實例時只有一個線程。

2、靜態內部類方式在Singleton類被裝載時並不會立即實例化,而是在需要實例化時,調用getInstance方法,纔會裝載SingletonInstance類,從而完成Singleton的實例化。

3、 類的靜態屬性只會在第一次加載類的時候初始化,所以在這裏,JVM幫助我們保證了線程的安全性,在類進行初始化時,別的線程是無法進入的。

4、優點:避免了線程不安全,利用靜態內部類特點實現延遲加載,效率高

5、結論:推薦使用

8、枚舉

1)代碼案例

public class SingleTon08 {

    public static void main(String[] args) {
        Singleton instance = Singleton.INSTANCE;
        Singleton instance2 = Singleton.INSTANCE;
        System.out.println(instance == instance2);
        System.out.println("instance.hashCode=" + instance.hashCode());
        System.out.println("instance2.hashCode=" + instance2.hashCode());
    }
}


enum Singleton{
    INSTANCE;

    public void hello(){
        System.out.println("hello");
    }
}

2)優點和缺點

1、這藉助JDK1.5中添加的枚舉來實現單例模式。不僅能避免多線程同步問題,而 且還能防止反序列化重新創建新的對象。

2、這種方式是Effective Java作者Josh Bloch 提倡的方式

3、結論:推薦使用

  • JDK中的單例模式

Runtime餓漢式(靜態常量)在系統中肯定能使用到不存在內存浪費,類加載的時候就進行了實例化。也不存在線程安全的問題。 

public class Runtime {
    private static Runtime currentRuntime = new Runtime();

    /**
     * Returns the runtime object associated with the current Java application.
     * Most of the methods of class <code>Runtime</code> are instance
     * methods and must be invoked with respect to the current runtime object.
     *
     * @return  the <code>Runtime</code> object associated with the current
     *          Java application.
     */
    public static Runtime getRuntime() {
        return currentRuntime;
    }

    /** Don't let anyone else instantiate this class */
    private Runtime() {}
.......
  • 總結

根據以上單例模式的八種方式,推薦使用的方式有枚舉、靜態內部類、雙重檢查、餓漢式(靜態常量)。

1) 單例模式保證了系統內存中該類只存在一個對象,節省了系統資源,對於一些需要頻繁創建銷燬的對象,使用單例模式可以提高系統性能

2) 當想實例化一個單例類的時候,必須要記住使用相應的獲取對象的方法,而不是使用new

3) 單例模式使用的場景:需要頻繁的進行創建和銷燬的對象、創建對象時耗時過多或耗費資源過多(即:重量級對象),但又經常用到的對象、工具類對象、頻繁訪問數據庫或文件的對象(比如數據源、session工廠等)

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