單例總結

雙重檢測

public class SingletonTest { 

    // 定義一個私有構造方法
    private SingletonTest() { 
     
    }   
    //定義一個靜態私有變量(不初始化,不使用final關鍵字,使用volatile保證了多線程訪問時instance變量的可見性,避免了instance初始化時其他變量屬性還沒賦值完時,被另外線程調用)
    private static volatile SingletonTest instance;  

    //定義一個共有的靜態方法,返回該類型實例
    public static SingletonTest getIstance() { 
        // 對象實例化時與否判斷(不使用同步代碼塊,instance不等於null時,直接返回對象,提高運行效率)
        if (instance == null) {
            //同步代碼塊(對象未初始化時,使用同步代碼塊,保證多線程訪問時對象在第一次創建後,不再重複被創建)
            synchronized (SingletonTest.class) {
                //未初始化,則初始instance變量
                if (instance == null) {
                    instance = new SingletonTest();   
                }   
            }   
        }   
        return instance;   
    }   
}

靜態內部類

public final class Service {

    /**
     * 在私有構造函數中進行一些初始配置
     */
    private Service() {
       
    }
    /**
     * 獲取Service
     *
     * @return
     */
    public static Service getInstance() {
        return ServiceHolder.service;
    }

    private static class ServiceHolder {
        private static final Service service = new Service();
    }
}
  • 雖然我們經常使用這種靜態內部類的懶加載方式,但其中的原理不一定每個人都清楚。接下來我們便來分析其原理,搞清楚兩個問題:
  • 靜態內部類方式是怎麼實現懶加載的
  • 靜態內部類方式是怎麼保證線程安全的
  • Java 類的加載過程包括:加載、驗證、準備、解析、初始化。初始化階段即執行類的 clinit 方法(clinit = class + initialize),包括爲類的靜態變量賦初始值和執行靜態代碼塊中的內容。但不會立即加載內部類,內部類會在使用時才加載。所以當此 Singleton 類加載時,SingletonHolder 並不會被立即加載,所以不會像餓漢式那樣佔用內存。
  • 另外,Java 虛擬機規定,當訪問一個類的靜態字段時,如果該類尚未初始化,則立即初始化此類。當調用Singleton 的 getInstance 方法時,由於其使用了 SingletonHolder 的靜態變量 instance,所以這時纔會去初始化 SingletonHolder,在 SingletonHolder 中 new 出 Singleton 對象。這就實現了懶加載
  • 第二個問題的答案是 Java 虛擬機的設計是非常穩定的,早已經考慮到了多線程併發執行的情況。虛擬機在加載類的 clinit 方法時,會保證 clinit 在多線程中被正確的加鎖、同步。即使有多個線程同時去初始化一個類,一次也只有一個線程可以執行 clinit 方法,其他線程都需要阻塞等待,從而保證了線程安全。
  • 一般的建議是:對於構建不復雜,加載完成後會立即使用的單例對象,推薦使用餓漢式。對於構建過程耗時較長,並不是所有使用此類都會用到的單例對象,推薦使用懶漢式。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章