【Java多線程編程實戰案例】單例模式

單例模式

單例模式所要實現的目標非常簡單:保持一個類有且僅有一個實例。注意,實現一個類有且僅有一個實例的前提是這個類是一個Java虛擬機實例中的一個Class Loader所加載的類。考慮到Java虛擬機的Class Loader機制:同一個類可以被多個Class Loader加載,這些Class Loader各自創建這個類的類實例。因此,如果有多個Class Loader加載同一個類,那麼所謂的“單例”就無法滿足——這些Class Loader各自的類實例都創建該類的唯一一個實例,實際上被創建的實例數就等於加載這個類的Class Loader的數量。

單線程版

出於性能考慮,不少單例模式的實現會採用延遲加載(Lazy Loading)的方式,即僅在需要用到相應實例的時候才創建實例。從單線程應用程序的角度理解,採用延遲加載實現的一個單例模式如下所示:

package com.company.ch3.case2;

public class SingleThreadedSingleton {
    // 保存該類的唯一實例
    private static SingleThreadedSingleton instance = null;

    //省略實例變量的聲明
    /*
    私有構造器使其他類無法直接通過new創建該類的實例
     */
    private SingleThreadedSingleton() {
        //什麼也不做
    }

    /**
     * 創建並返回該類的唯一實例
     * 即只有該方法被調用時該類的唯一實例纔會被創建
     *
     * @return
     */
    public static SingleThreadedSingleton getInstance() {
        if (null == instance) { //操作①
            instance = new SingleThreadedSingleton(); //操作②
        }
        return instance;
    }

    public void someService() {
        //省略其他代碼
    }
}

簡單加鎖實現

package com.company.ch3.case2;

public class SimpleMultithreadedSingleton {
    // 保存該類的唯一實例
    private static SimpleMultithreadedSingleton instance = null;

    /**
     * 私有構造器使其他類無法直接通過new創建該類的實例
     */
    private SimpleMultithreadedSingleton() {
        //什麼也不做
    }

    /**
     * 創建並返回該類的唯一實例
     * 即只有該方法被調用時該類的唯一實例纔會被創建
     *
     * @return
     */
    public static SimpleMultithreadedSingleton getInstance() {
        synchronized (SimpleMultithreadedSingleton.class) {
            if (null == instance) {
                instance = new SimpleMultithreadedSingleton();
            }
        }
        return instance;
    }

    public void someService() {
        //省略其他代碼
    }
}

這種方法實現的單例模式意味着getInstance()的任何一個執行線程都需要申請鎖。

基於雙重檢查鎖定的單例模式

package com.company.ch3.case2;

public class DCLSingleton {
    /**
     * 保存該類的唯一實例,使用volatile關鍵字修飾instance
     */
    private static volatile DCLSingleton instance;

    /**
     * 私有構造器使其他類無法直接通過new創建該類的實例
     */
    private DCLSingleton() {
        //什麼也不做
    }

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

    private void someService() {
        //省略其他代碼
    }
}

將instance變量採用volatile修飾實際上是利用了volatile關鍵字的以下兩個作用:

  • 保障可見性。一個線程通過執行操作修改了instance變量值,其他線程可以讀取到相應的值。
  • 保障有序性。由於volatile能夠禁止volatile變量寫操作與該操作之前的任何讀、寫操作進行重排序。因此volatile修飾instance相當於禁止JIT編譯器以及處理器將對對象進行初始化的寫操作重排序到將對象引用寫入共享變量的寫操作,這保障了一個線程讀取到instance變量所引用的實例時該實例已經初始化完畢。

基於靜態內部類的單例模式實現

package com.company.ch3.case2;

import util.Debug;

public class StaticHolderSingleton {
    // 私有構造器
    private StaticHolderSingleton() {
        Debug.info("StaticHolderSingleton inited.");
    }

    private static class InstanceHolder {
        //保存外部類的唯一實例
        final static StaticHolderSingleton INSTANCE = new StaticHolderSingleton();
    }

    public static StaticHolderSingleton getInstance() {
        Debug.info("getInstance invoked");
        return InstanceHolder.INSTANCE;
    }

    private void someService() {
        Debug.info("SomeService invoked.");
        //省略其他代碼
    }

    public static void main(String[] args) {
        StaticHolderSingleton.getInstance().someService();
    }
}

類的靜態變量被初次訪問會觸發Java虛擬機對該類進行初始化,即該類的靜態變量的值會變爲其初始值而不是默認值。因此,靜態方法getInstance()被調用的時候Java虛擬機會初始化這個方法所訪問的內部靜態類InstanceHolder。這使得InstanceHolder的靜態內部類INSTANCE被初始化,從而使StaticHolderSingleton類的唯一實例得以創建。由於類的靜態變量只會創建一次,因此StaticHolderSingleton只會被創建一次。

基於枚舉類型的單例模式

package com.company.ch3.case2;

import util.Debug;

public class EnumBasedSingleton {
    public static void main(String[] args) {
        Thread t = new Thread() {
            @Override
            public void run() {
                Debug.info(Singleton.class.getName());
                Singleton.INSTANCE.someService();
            }
        };
        t.start();
    }

    public static enum Singleton {
        INSTANCE;

        // 私有構造器
        Singleton() {
            Debug.info("Singleton inited.");
        }

        public void someService() {
            Debug.info("someService invoked.");
            // 省略其他代碼
        }
    }
}

這裏的枚舉類型Singleton相當於一個單例類,其字段INSTANCE值相當於該類的唯一實例。這個實例是在Singleton.INSTANCE初次被引用的時候才被初始化的。僅訪問Singleton本身並不會導致Singleton的唯一實例被初始化。

參考資料

《Java多線程編程實戰指南》

發佈了243 篇原創文章 · 獲贊 140 · 訪問量 25萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章