天天看點

寂然解讀設計模式 - 單例模式(下)

I walk very slowly, but I never walk backwards            

設計模式 - 單例模式(下)

​ 寂然

大家好~,我是寂然,本節課呢,我們接着來聊單例模式,本節課的重點是單例模式最後兩種寫法,靜态内部類和枚舉,接着帶大家閱讀JDK源碼中單例模式的應用,以及對單例模式的注意事項進行總結,那我們啟程吧

靜态内部類

通過靜态内部類,同樣可以實作單例模式,首先大家要對靜态内部類有了解, 用static修飾的内部類,稱為靜态内部類, 我們先編寫代碼,驗證其正确性,然後對靜态内部類的寫法進行分析,示例代碼如下:

// 單例模式 - 靜态内部類
class Singleton{

    private Singleton(){

    }

    private static class SingletonInstance {

        public static final Singleton INSTANCE = new Singleton();

    }

    public static Singleton getInstance(){
        return SingletonInstance.INSTANCE;
    }
}

public class InnerClassDemo {
    public static void main(String[] args) {

        Singleton instance = Singleton.getInstance();
        Singleton instance1 = Singleton.getInstance();
        System.out.println(instance == instance1);
    }
}           
寫法分析

靜态内部類的特點是,當Singleton類進行類加載的時候,靜态内部類是不會被加載的

當調用Singleton類的 getInstance() 方法,用到了 SingletonInstance 的靜态變量的時候,會導緻靜态内部類SingletonInstance 進行類加載,當然類加載的過程中,線程是安全的,是以這種寫法不會出現線程安全問題

這種方式采用類加載的機制來保證初始化執行個體時隻有一個線程, 類的靜态屬性隻會在第一次加載類的時候初始化,是以在這裡,JVM 幫助我們保證了線程的安全性,在類進行初始化時,别的線程是無法進入的 ,避免了線程不安全,利用靜态内部類特點實作延遲加載,效率也較高,是以這種方式也是推薦使用的

枚舉方式

通過枚舉的方式,其實也可以實作單例模式,這是單例模式的第八種寫法,示例代碼如下

//單例模式 - 枚舉方式
enum Singleton{

    INSTANCE; //屬性

    public void method(){
        System.out.println("執行個體方法的列印");
    }
}

public class EnumDemo {
    public static void main(String[] args) {

        Singleton instance = Singleton.INSTANCE;
        Singleton instance1 = Singleton.INSTANCE;
        System.out.println(instance == instance1);
        instance.method();
    }
}           

這借助 JDK1.5 中添加的枚舉來實作單例模式,不僅能避免多線程同步問題,而且還能防止反序列化重新建立新的對象,這種方式是 Effective Java 作者 Josh Bloch 提倡的方式,在實際開發中,同樣推薦使用這種方式

JDK源碼分析

JDK 中,java.lang.Runtime 就是經典的餓漢式單例模式,我們寫一段測試代碼,然後進行源碼分析

public class Test {
    public static void main(String[] args) {

        //得到一些系統資訊
        Runtime runtime = Runtime.getRuntime();
        int processors = runtime.availableProcessors();
        long freeMemory = runtime.freeMemory();
        long maxMemory = runtime.maxMemory();

        System.out.println("freeMemory " + freeMemory); //空閑記憶體
        System.out.println("maxMemory " + maxMemory);   //最大記憶體
        System.out.println("processors " + processors);  //處理器個數

    }
}           

通過源碼大家可以看到,Runtime 就是經典的餓漢式寫法,首先Runtime類 java 中肯定會用到,不存在浪費,其次,餓漢式的寫法,類的加載過程中建立對象,避免了線程安全問題

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

單例模式使用場景
  • 需要頻繁的進行建立和銷毀的對象
  • 建立對象時耗時過多或耗費資源過多(即:重量級對象)
  • 經常用到的對象
  • 工具類對象
  • 頻繁通路資料庫或檔案的對象(比如資料源、session 工廠等)

下節預告

OK,到這裡,單例模式就正式完結了,我們從單例模式的八種寫法入手,對每一種進行利弊分析,着重講解了雙重檢查機制,最後,我們看了JDK源碼中單例模式的使用,以及給大家強調了單例模式的注意事項,涉及的内容相對比較完整全面,下一節,我們進入第二個設計模式 - 工廠模式的學習,最後,希望大家在學習的過程中,能夠感覺到設計模式的有趣之處,高效而愉快的學習,那我們下期見~