java設計模式之單例模式你真的會了嗎?(懶漢式篇)


java設計模式之單例模式你真的會了嗎?(懶漢式篇)

一、什么是單例模式?

單例模式(Singleton Pattern)是 Java 中最簡單的設計模式之一。這種類型的設計模式屬於創建型模式,它提供了一種創建對象的最佳方式。
這種模式涉及到一個單一的類,該類負責創建自己的對象,同時確保只有單個對象被創建。這個類提供了一種訪問其唯一的對象的方式,可以直接訪問,不需要實例化該類的對象。

二、單例模式之懶漢式有什么特點以及優缺點?

  • 構造方法私有化
  • 在第一次被使用時構建實例,延遲初始化
  • 對外提供統一的靜態工廠方法返回實例
  • 優點:需要的時候才實例化所以節約內存。
  • 缺點:第一次加載時不夠快,多線程使用時不必要的同步開銷大。

三、懶漢式單例的代碼進階(1)

public class LazySingleton implements Serializable {
    private static final long serialVersionUID = -777413485350310911L;

    private LazySingleton() {}

    private static LazySingleton lazySingleton = null;

    public static LazySingleton getInstance() {
        if(lazySingleton == null){
            lazySingleton = new LazySingleton();
        }
        return lazySingleton;
    }
}
  • 上面的代碼存在着最明顯的問題就是在多線程的環境下無法保證單例。

四、懶漢式單例的代碼進階(2)

public class LazySingleton implements Serializable {
    private static final long serialVersionUID = -777413485350310911L;

    private LazySingleton() {}

    private volatile static LazySingleton lazySingleton = null;

    synchronized public static LazySingleton getInstance() {
        if(lazySingleton == null){
            lazySingleton = new LazySingleton();
        }
        return lazySingleton;
    }
}
  • 上面的代碼在getInstance()方法前面增加了關鍵字synchronized進行線程鎖,以處理多個線程同時訪問的問題。但是,這樣雖然解決了線程安全問題,但是每次調用getInstance()時都需要進行線程鎖定判斷,在多線程高並發訪問環境中,將會導致系統性能大大降低。

五、懶漢式單例的代碼進階(3)

public class LazySingleton implements Serializable {
    private static final long serialVersionUID = -777413485350310911L;

    private LazySingleton() {}

    private volatile static LazySingleton lazySingleton = null;

    //double check
    public static LazySingleton getInstance() {
        if(lazySingleton == null){
            synchronized(LazySingleton.class){
                if(lazySingleton == null){
                    lazySingleton = new LazySingleton();
                }
            }
        }
        return lazySingleton;
    }
}
  • 上面的代碼采用synchronized和double check的方式成功解決了線程安全問題以及提高了多線程下的性能,但是性能任然不夠理想。
  • lazySingleton采用 volatile 關鍵字修飾也是很有必要的, lazySingleton = new LazySingleton(); 這段代碼其實是分為三步執行:
    (1)為 lazySingleton 分配內存空間
    (2)初始化 lazySingleton
    (3)將 lazySingleton 指向分配的內存地址
    但是由於 JVM 具有指令重排的特性,執行順序有可能變成 1->3->2。指令重排在單線程環境下不會出現問題,但是在多線程環境下會導致一個線程獲得還沒有初始化的實例。例如,線程 T1 執行了 1 和 3,此時 T2 調用 getInstance() 后發現
    lazySingleton 不為空,因此返回 lazySingleton,但此時 lazySingleton 還未被初始化。使用 volatile 可以禁止 JVM 的指令重排,保證在多線程環境下也能正常運行。

六、懶漢式單例的代碼進階(4)

public class LazySingleton implements Serializable {
    private static final long serialVersionUID = -777413485350310911L;

    private LazySingleton() {}

    public static LazySingleton getInstance() {
        return LazyInnerSingleton.INSTANCE;
    }

    /**
     * 利用內部類的特性創建單例
     */
    private static class LazyInnerSingleton {
        private static final LazySingleton INSTANCE = new LazySingleton();
    }
}
  • 上面利用內部類的特性創建單例既保證了線程安全(由jvm的類加載機制提供)問題又提高了性能,但是還是存在着一個問題:利用反射依然可以破壞“單例”,所以繼續改進代碼。

六、懶漢式單例的代碼進階(5)

public class LazySingleton implements Serializable {
    private static final long serialVersionUID = -777413485350310911L;

    private LazySingleton() {
        //防止利用反射破壞單例
        if(LazyInnerSingleton.INSTANCE != null){
            throw new RuntimeException("不允許構建多個實例!");
        }
    }

    public static LazySingleton getInstance() {
        return LazyInnerSingleton.INSTANCE;
    }

    /**
     * 利用內部類的特性創建單例
     */
    private static class LazyInnerSingleton {
        private static final LazySingleton INSTANCE = new LazySingleton();
    }
}
  • 在構造方法中拋出一個異常以防止通過反射破壞單例。

PS:如果你看到了這篇文章,並且覺得對你有幫助,請給個關注和點贊,謝謝!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM