JAVA線程安全的單例

1、單例的含義

單例模式是爲確保一個類只有一個實例,併爲整個系統提供一個全局訪問點的一種模式方法。
  從概念中體現出了單例的一些特色:
(1)、在任何狀況下,單例類永遠只有一個實例存在
(2)、單例須要有能力爲整個系統提供這一惟一實例  

   在計算機系統中,**線程池、緩存、日誌對象、對話框、打印機、顯卡的驅動程序對象**常被設計成單例。這些應用都或多或少具備資源管理器的功能。每臺計算機能夠有若干個打印機,但只能有一個Printer Spooler,以免兩個打印做業同時輸出到打印機中。每臺計算機能夠有若干通訊端口,系統應當集中管理這些通訊端口,以免一個通訊端口同時被兩個請求同時調用。總之,選擇單例模式就是爲了不不一致狀態,避免政出多頭。

   正是因爲這個特色,單例對象一般做爲程序中的存放配置信息的載體,由於它能保證其餘對象讀到一致的信息。例如在某個服務器程序中,該服務器的配置信息可能存放在數據庫或文件中,這些配置數據由某個單例對象統一讀取,服務進程中的其餘對象若是要獲取這些配置信息,只需訪問該單例對象便可。這種方式極大地簡化了在複雜環境下,尤爲是多線程環境下的配置管理,可是隨着應用場景的不一樣,也可能帶來一些同步問題。

2、單例實現

一、餓漢式單例

餓漢式單例(G:簡單有效安全)是指在方法調用前,實例就已經建立好了。
簡單安全,私有化構造方法,私有化一個實例。數據庫

public class MySingleton {
    private static MySingleton instance = new MySingleton();
    private MySingleton(){

    }

    public static MySingleton getInstance() {
        return instance;
    }
}

二、懶漢式單例

懶漢式單例是指在方法調用獲取實例時才建立實例,由於相對餓漢式顯得「不急迫」,因此被叫作「懶漢模式」。不是線程安全的。編程

public class MySingletonLazy {
    private static MySingletonLazy instance = null;
    private MySingletonLazy(){}

    public static MySingletonLazy getInstance(){
        if (instance == null){
            instance = new MySingletonLazy();
        }
        return instance;
    }
}

這裏實現了懶漢式的單例,可是熟悉多線程併發編程的朋友應該能夠看出,在多線程併發下這樣的實現是沒法保證明例實例惟一的,甚至能夠說這樣的實現是徹底錯誤的,在實例化須要耗時的狀況下,並不能保證建立的是一個實例。設計模式

三、線程安全的懶漢式單例

要保證線程安全,咱們就得須要使用同步鎖機制,下面就來看看咱們如何一步步的解決存在線程安全問題的懶漢式單例(錯誤的單例)。緩存

(1)方法中聲明synchronized關鍵字

出現線程安全問題,是因爲多個線程能夠同時進入getInstance()方法,那麼只須要對該方法進行synchronized的鎖同步便可:安全

public class MySingletonLazySafe {
    private static MySingletonLazySafe instance = null;
    private MySingletonLazySafe(){}

    public synchronized static MySingletonLazySafe getInstance(){
            if (instance == null){
                instance = new MySingletonLazySafe();
            }
        return instance;
    }
}

問題已經解決了,可是這種實現方式的運行效率會很低。同步方法效率低,那咱們考慮使用同步代碼塊來實現。服務器

(2)同步代碼塊實現

public class MySingletonLazySafe {
    private static MySingletonLazySafe instance = null;
    private MySingletonLazySafe(){}

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

這裏的實現可以保證多線程併發下的線程安全性,可是這樣的實現將所有的代碼都被鎖上了,一樣的效率很低下。多線程

(3)使用靜態內部類實現單例模式

public class MySingletonLazySafeByInnerClass {

    private static class MySingletonHandler{
        private static MySingletonLazySafeByInnerClass instance = new MySingletonLazySafeByInnerClass();
    }

    private MySingletonLazySafeByInnerClass(){}

    public static MySingletonLazySafeByInnerClass getInstance(){
        return MySingletonHandler.instance;
    }
}

靜態內部類實現的單例在多線程併發下單個實例獲得了保證。 併發

(4)使用static代碼塊實現單例

public class MySingletonLazy {
    private static MySingletonLazy instance = null;
    private MySingletonLazy(){}

    static {
        instance = new MySingletonLazy();
    }

    public static MySingletonLazy getInstance(){
        return instance;
    }
}

靜態代碼塊中的代碼在使用類的時候就已經執行了,因此能夠應用靜態代碼塊的這個特性的實現單例設計模式。ide

(5)使用枚舉數據類型實現單例模式

public enum EnumFactory {
    singletonFactory;
    private MySingletonLazy instance;

    private EnumFactory(){
        instance = new MySingletonLazy();
    }

    public MySingletonLazy getInstance(){
        return instance;
    }
    public class MySingletonLazy{
        public MySingletonLazy(){}
    }
}
引用:
EnumFactory.MySingletonLazy singletonLazy =  EnumFactory.singletonFactory.getInstance();

枚舉enum和靜態代碼塊的特性類似,在使用枚舉時,構造方法會被自動調用,利用這一特性也能夠實現單例。可是這樣寫枚舉類被徹底暴露了,聽說違反了「職責單一原則」,那咱們來看看怎麼進行改造呢。線程

(6)完善使用枚舉實現單例模式

public class ClassFactory {
    private enum MyEnumSingleton{
        singletonFactory;
        private MySingleton1 instance;
        private MyEnumSingleton(){ //枚舉類的構造方法在類加載時被實例化
            instance = new MySingleton1();
        }
        public MySingleton1 getInstance(){
            return instance;
        }
    }

    public static MySingleton1 getInstance(){
        return MyEnumSingleton.singletonFactory.getInstance();
    }
}
public class MySingleton1 {
    //須要獲實現單例的類,好比數據庫鏈接Connection
    public MySingleton1(){

    }
}

(7)序列化與反序列化的單例模式實現

靜態內部類雖然保證了單例在多線程併發下的線程安全性,可是在遇到序列化對象時,默認的方式運行獲得的結果就是多例的。

代碼實現以下:

public class MySingletonLazySafeByInnerClass implements Serializable {

    private static final long serialVersionUID = 1L;

    //靜態內部類
    private static class MySingletonHandler{
        private static MySingletonLazySafeByInnerClass instance = new MySingletonLazySafeByInnerClass();
    }

    private MySingletonLazySafeByInnerClass(){}

    public static MySingletonLazySafeByInnerClass getInstance(){
        return MySingletonHandler.instance;
    }
}

經驗證序列號對象的hashCode和反序列化後獲得的對象的hashCode值不同,說明反序列化後返回的對象是從新實例化的,單例被破壞了。

解決辦法就是在反序列化的過程當中使用readResolve()方法,單例實現的代碼以下:

public class MySingletonLazySafeByInnerClass implements Serializable {

    private static final long serialVersionUID = 1L;

    //靜態內部類
    private static class MySingletonHandler{
        private static MySingletonLazySafeByInnerClass instance = new MySingletonLazySafeByInnerClass();
    }

    private MySingletonLazySafeByInnerClass(){}

    public static MySingletonLazySafeByInnerClass getInstance(){
        return MySingletonHandler.instance;
    }

    protected Object readResolve() throws ObjectStreamException {
        System.out.println("調用了readResolve方法! ");
        return MySingletonHandler.instance;
    }

    public static void main(String[] args) {
        MySingletonLazySafeByInnerClass safeByInnerClass = MySingletonLazySafeByInnerClass.getInstance();
        File file = new File("MySingleton.txt");
        try {
            FileOutputStream fos = new FileOutputStream(file);
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(safeByInnerClass);
            oos.close();
            fos.close();
            System.out.println(safeByInnerClass.hashCode());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        try {
            FileInputStream fis = new FileInputStream(file);
            ObjectInputStream ois = new ObjectInputStream(fis);
            MySingletonLazySafeByInnerClass rsafeByInnerClass = (MySingletonLazySafeByInnerClass) ois.readObject();
            ois.close();;
            fis.close();
            System.out.println(rsafeByInnerClass.hashCode());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

}
相關文章
相關標籤/搜索