設計模式 | 單例模式及典型應用

單例是最多見的設計模式之一,實現的方式很是多,同時須要注意的問題也很是多。html

本文主要內容:java

  • 介紹單例模式
  • 介紹單例模式的N中寫法
  • 單例模式的安全性
    • 序列化攻擊
    • 反射攻擊
  • 單例模式總結
  • 介紹單例模式的典型應用

單例模式

單例模式(Singleton Pattern):確保某一個類只有一個實例,並且自行實例化並向整個系統提供這個實例,這個類稱爲單例類,它提供全局訪問的方法。單例模式是一種對象建立型模式。算法

單例模式有三個要點:spring

  1. 構造方法私有化;
  2. 實例化的變量引用私有化;
  3. 獲取實例的方法共有

角色

Singleton(單例):在單例類的內部實現只生成一個實例,同時它提供一個靜態的 getInstance() 工廠方法,讓客戶能夠訪問它的惟一實例;爲了防止在外部對其實例化,將其構造函數設計爲私有;在單例類內部定義了一個 Singleton 類型的靜態對象,做爲外部共享的惟一實例。設計模式

單例模式的七種寫法

一、餓漢式

// 線程安全
public class Singleton {

    private final static Singleton INSTANCE = new Singleton();

    private Singleton(){}

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

優勢:簡單,使用時沒有延遲;在類裝載時就完成實例化,天生的線程安全瀏覽器

缺點:沒有懶加載,啓動較慢;若是從始至終都沒使用過這個實例,則會形成內存的浪費。安全

二、餓漢式變種

// 線程安全
public class Singleton {

    private static Singleton instance;

    static {
        instance = new Singleton();
    }

    private Singleton() {}

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

將類實例化的過程放在了靜態代碼塊中,在類裝載的時執行靜態代碼塊中的代碼,初始化類的實例。優缺點同上。微信

三、懶漢式

// 線程不安全
public class Singleton {

    private static Singleton singleton;

    private Singleton() {}

    public static Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}

優勢:懶加載,啓動速度快、若是從始至終都沒使用過這個實例,則不會初始化該實力,可節約資源mybatis

缺點:多線程環境下線程不安全。if (singleton == null) 存在競態條件,可能會有多個線程同時進入 if 語句,致使產生多個實例多線程

四、懶漢式變種

// 線程安全,效率低
public class Singleton {

    private static Singleton singleton;

    private Singleton() {}

    public static synchronized Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}

優勢:解決了上一種實現方式的線程不安全問題

缺點:synchronized 對整個 getInstance() 方法都進行了同步,每次只有一個線程可以進入該方法,併發性能極差

五、雙重檢查鎖

// 線程安全
public class Singleton {
    // 注意:這裏有 volatile 關鍵字修飾
    private static volatile Singleton singleton;

    private Singleton() {}

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

優勢:線程安全;延遲加載;效率較高。

因爲 JVM 具備指令重排的特性,在多線程環境下可能出現 singleton 已經賦值但還沒初始化的狀況,致使一個線程得到尚未初始化的實例。volatile 關鍵字的做用:

  • 保證了不一樣線程對這個變量進行操做時的可見性
  • 禁止進行指令重排序

六、靜態內部類

// 線程安全
public class Singleton {

    private Singleton() {}

    private static class SingletonInstance {
        private static final Singleton INSTANCE = new Singleton();
    }

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

優勢:避免了線程不安全,延遲加載,效率高。

靜態內部類的方式利用了類裝載機制來保證線程安全,只有在第一次調用getInstance方法時,纔會裝載SingletonInstance內部類,完成Singleton的實例化,因此也有懶加載的效果。

加入參數 -verbose:class 能夠查看類加載順序

$ javac Singleton.java
$ java -verbose:class Singleton

七、枚舉

// 線程安全
public enum Singleton {
    INSTANCE;
    public void whateverMethod() {
    }
}

優勢:經過JDK1.5中添加的枚舉來實現單例模式,寫法簡單,且不只能避免多線程同步問題,並且還能防止反序列化從新建立新的對象

單例模式的安全性

單例模式的目標是,任什麼時候候該類都只有惟一的一個對象。可是上面咱們寫的大部分單例模式都存在漏洞,被攻擊時會產生多個對象,破壞了單例模式。

序列化攻擊

經過Java的序列化機制來攻擊單例模式

public class HungrySingleton {
    private static final HungrySingleton instance = new HungrySingleton();
    private HungrySingleton() {
    }
    public static HungrySingleton getInstance() {
        return instance;
    }

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        HungrySingleton singleton = HungrySingleton.getInstance();
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton_file"));
        oos.writeObject(singleton); // 序列化

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("singleton_file"));
        HungrySingleton newSingleton = (HungrySingleton) ois.readObject(); // 反序列化

        System.out.println(singleton);
        System.out.println(newSingleton);
        System.out.println(singleton == newSingleton);
    }
}

結果

com.singleton.HungrySingleton@ed17bee
com.singleton.HungrySingleton@46f5f779
false

Java 序列化是如何攻擊單例模式的呢?咱們須要先複習一下Java的序列化機制

Java 序列化機制

java.io.ObjectOutputStream 是Java實現序列化的關鍵類,它能夠將一個對象轉換成二進制流,而後能夠經過 ObjectInputStream 將二進制流還原成對象。具體的序列化過程不是本文的重點,在此僅列出幾個要點。

Java 序列化機制的要點

  • 須要序列化的類必須實現java.io.Serializable接口,不然會拋出NotSerializableException異常
  • 若沒有顯示地聲明一個serialVersionUID變量,Java序列化機制會根據編譯時的class自動生成一個serialVersionUID做爲序列化版本比較(驗證一致性),若是檢測到反序列化後的類的serialVersionUID和對象二進制流的serialVersionUID不一樣,則會拋出異常
  • Java的序列化會將一個類包含的引用中全部的成員變量保存下來(深度複製),因此裏面的引用類型必須也要實現java.io.Serializable接口
  • 當某個字段被聲明爲transient後,默認序列化機制就會忽略該字段,反序列化後自動得到0或者null值
  • 靜態成員不參與序列化
  • 每一個類能夠實現readObjectwriteObject方法實現本身的序列化策略,即便是transient修飾的成員變量也能夠手動調用ObjectOutputStreamwriteInt等方法將這個成員變量序列化。
  • 任何一個readObject方法,不論是顯式的仍是默認的,它都會返回一個新建的實例,這個新建的實例不一樣於該類初始化時建立的實例
  • 每一個類能夠實現private Object readResolve()方法,在調用readObject方法以後,若是存在readResolve方法則自動調用該方法,readResolve將對readObject的結果進行處理,而最終readResolve的處理結果將做爲readObject的結果返回。readResolve的目的是保護性恢復對象,其最重要的應用就是保護性恢復單例、枚舉類型的對象
  • Serializable接口是一個標記接口,可自動實現序列化,而Externalizable繼承自Serializable,它強制必須手動實現序列化和反序列化算法,相對來講更加高效
序列化破壞單例模式的解決方案

根據上面對Java序列化機制的複習,咱們能夠自定義一個 readResolve,在其中返回類的單例對象,替換掉 readObject 方法反序列化生成的對象,讓咱們本身寫的單例模式實現保護性恢復對象

public class HungrySingleton implements Serializable {
    private static final HungrySingleton instance = new HungrySingleton();
    private HungrySingleton() {
    }
    public static HungrySingleton getInstance() {
        return instance;
    }

    private Object readResolve() {
        return instance;
    }

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        HungrySingleton singleton = HungrySingleton.getInstance();
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("singleton_file"));
        HungrySingleton newSingleton = (HungrySingleton) ois.readObject();

        System.out.println(singleton);
        System.out.println(newSingleton);
        System.out.println(singleton == newSingleton);
    }
}

再次運行

com.singleton.HungrySingleton@24273305
com.singleton.HungrySingleton@24273305
true

注意:本身實現的單例模式都須要避免被序列化破壞

反射攻擊

在單例模式中,構造器都是私有的,而反射能夠經過構造器對象調用 setAccessible(true) 來得到權限,這樣就能夠建立多個對象,來破壞單例模式了

public class HungrySingleton {
    private static final HungrySingleton instance = new HungrySingleton();

    private HungrySingleton() {
    }

    public static HungrySingleton getInstance() {
        return instance;
    }

    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        HungrySingleton instance = HungrySingleton.getInstance();
        Constructor constructor = HungrySingleton.class.getDeclaredConstructor();
        constructor.setAccessible(true);    // 得到權限
        HungrySingleton newInstance = (HungrySingleton) constructor.newInstance();

        System.out.println(instance);
        System.out.println(newInstance);
        System.out.println(instance == newInstance);
    }
}

輸出結果

com.singleton.HungrySingleton@3b192d32
com.singleton.HungrySingleton@16f65612
false
反射攻擊解決方案

反射是經過它的Class對象來調用構造器建立新的對象,咱們只須要在構造器中檢測並拋出異常就能夠達到目的了

private HungrySingleton() {
    // instance 不爲空,說明單例對象已經存在
    if (instance != null) {
        throw new RuntimeException("單例模式禁止反射調用!");
    }
}

運行結果

Exception in thread "main" java.lang.reflect.InvocationTargetException
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
	at com.singleton.HungrySingleton.main(HungrySingleton.java:32)
Caused by: java.lang.RuntimeException: 單例模式禁止反射調用!
	at com.singleton.HungrySingleton.<init>(HungrySingleton.java:20)
	... 5 more

注意,上述方法針對餓漢式單例模式是有效的,但對懶漢式的單例模式是無效的,懶漢式的單例模式是沒法避免反射攻擊的!

爲何對餓漢有效,對懶漢無效?由於餓漢的初始化是在類加載的時候,反射必定是在餓漢初始化以後才能使用;而懶漢是在第一次調用 getInstance() 方法的時候才初始化,咱們沒法控制反射和懶漢初始化的前後順序,若是反射在前,無論反射建立了多少對象,instance都將一直爲null,直到調用 getInstance()

事實上,實現單例模式的惟一推薦方法,是使用枚舉類來實現。

爲何推薦使用枚舉單例

寫下咱們的枚舉單例模式

package com.singleton;

import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public enum SerEnumSingleton implements Serializable {
    INSTANCE;   // 單例對象
    private String content;

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    private SerEnumSingleton() {
    }

    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        SerEnumSingleton singleton1 = SerEnumSingleton.INSTANCE;
        singleton1.setContent("枚舉單例序列化");
        System.out.println("枚舉序列化前讀取其中的內容:" + singleton1.getContent());
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("SerEnumSingleton.obj"));
        oos.writeObject(singleton1);
        oos.flush();
        oos.close();

        FileInputStream fis = new FileInputStream("SerEnumSingleton.obj");
        ObjectInputStream ois = new ObjectInputStream(fis);
        SerEnumSingleton singleton2 = (SerEnumSingleton) ois.readObject();
        ois.close();
        System.out.println(singleton1 + "\n" + singleton2);
        System.out.println("枚舉序列化後讀取其中的內容:" + singleton2.getContent());
        System.out.println("枚舉序列化先後兩個是否同一個:" + (singleton1 == singleton2));

        Constructor<SerEnumSingleton> constructor = SerEnumSingleton.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        SerEnumSingleton singleton3 = constructor.newInstance(); // 經過反射建立對象
        System.out.println("反射後讀取其中的內容:" + singleton3.getContent());
        System.out.println("反射先後兩個是否同一個:" + (singleton1 == singleton3));
    }
}

運行結果,序列化先後的對象是同一個對象,而反射的時候拋出了異常

枚舉序列化前讀取其中的內容:枚舉單例序列化
INSTANCE
INSTANCE
枚舉序列化後讀取其中的內容:枚舉單例序列化
枚舉序列化先後兩個是否同一個:true
Exception in thread "main" java.lang.NoSuchMethodException: com.singleton.SerEnumSingleton.<init>()
	at java.lang.Class.getConstructor0(Class.java:3082)
	at java.lang.Class.getDeclaredConstructor(Class.java:2178)
	at com.singleton.SerEnumSingleton.main(SerEnumSingleton.java:39)

編譯後,再經過 JAD 進行反編譯獲得下面的代碼

// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3) 
// Source File Name:   SerEnumSingleton.java

package com.singleton;

import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public final class SerEnumSingleton extends Enum
    implements Serializable
{

    public static SerEnumSingleton[] values()
    {
        return (SerEnumSingleton[])$VALUES.clone();
    }

    public static SerEnumSingleton valueOf(String name)
    {
        return (SerEnumSingleton)Enum.valueOf(com/singleton/SerEnumSingleton, name);
    }

    public String getContent()
    {
        return content;
    }

    public void setContent(String content)
    {
        this.content = content;
    }

    private SerEnumSingleton(String s, int i)
    {
        super(s, i);
    }

    public static void main(String args[])
        throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException
    {
        SerEnumSingleton singleton1 = INSTANCE;
        singleton1.setContent("\u679A\u4E3E\u5355\u4F8B\u5E8F\u5217\u5316");
        System.out.println((new StringBuilder()).append("\u679A\u4E3E\u5E8F\u5217\u5316\u524D\u8BFB\u53D6\u5176\u4E2D\u7684\u5185\u5BB9\uFF1A").append(singleton1.getContent()).toString());
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("SerEnumSingleton.obj"));
        oos.writeObject(singleton1);
        oos.flush();
        oos.close();
        FileInputStream fis = new FileInputStream("SerEnumSingleton.obj");
        ObjectInputStream ois = new ObjectInputStream(fis);
        SerEnumSingleton singleton2 = (SerEnumSingleton)ois.readObject();
        ois.close();
        System.out.println((new StringBuilder()).append(singleton1).append("\n").append(singleton2).toString());
        System.out.println((new StringBuilder()).append("\u679A\u4E3E\u5E8F\u5217\u5316\u540E\u8BFB\u53D6\u5176\u4E2D\u7684\u5185\u5BB9\uFF1A").append(singleton2.getContent()).toString());
        System.out.println((new StringBuilder()).append("\u679A\u4E3E\u5E8F\u5217\u5316\u524D\u540E\u4E24\u4E2A\u662F\u5426\u540C\u4E00\u4E2A\uFF1A").append(singleton1 == singleton2).toString());
        Constructor constructor = com/singleton/SerEnumSingleton.getDeclaredConstructor(new Class[0]);
        constructor.setAccessible(true);
        SerEnumSingleton singleton3 = (SerEnumSingleton)constructor.newInstance(new Object[0]);
        System.out.println((new StringBuilder()).append("\u53CD\u5C04\u540E\u8BFB\u53D6\u5176\u4E2D\u7684\u5185\u5BB9\uFF1A").append(singleton3.getContent()).toString());
        System.out.println((new StringBuilder()).append("\u53CD\u5C04\u524D\u540E\u4E24\u4E2A\u662F\u5426\u540C\u4E00\u4E2A\uFF1A").append(singleton1 == singleton3).toString());
    }

    public static final SerEnumSingleton INSTANCE;
    private String content;
    private static final SerEnumSingleton $VALUES[];

    static 
    {
        INSTANCE = new SerEnumSingleton("INSTANCE", 0);
        $VALUES = (new SerEnumSingleton[] {
            INSTANCE
        });
    }
}

經過反編譯後代碼咱們能夠看到,ublic final class T extends Enum,說明,當咱們使用enmu來定義一個枚舉類型的時候,編譯器會自動幫咱們建立一個final類型的類繼承Enum類,因此枚舉類型不能被繼承。

那麼,爲何推薦使用枚舉單例呢?

1. 枚舉單例寫法簡單

2. 線程安全&懶加載

代碼中 INSTANCE 變量被 public static final 修飾,由於static類型的屬性是在類加載以後初始化的,JVM能夠保證線程安全;且Java類是在引用到的時候才進行類加載,因此枚舉單例也有懶加載的效果。

3. 枚舉本身能避免序列化攻擊

爲了保證枚舉類型像Java規範中所說的那樣,每個枚舉類型極其定義的枚舉變量在JVM中都是惟一的,在枚舉類型的序列化和反序列化上,Java作了特殊的規定。

在序列化的時候Java僅僅是將枚舉對象的name屬性輸出到結果中,反序列化的時候則是經過java.lang.Enum的valueOf方法來根據名字查找枚舉對象。同時,編譯器是不容許任何對這種序列化機制的定製,所以禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法。 咱們看一下Enum類的valueOf方法:

public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name) {
        T result = enumType.enumConstantDirectory().get(name);
        if (result != null)
            return result;
        if (name == null)
            throw new NullPointerException("Name is null");
        throw new IllegalArgumentException(
            "No enum constant " + enumType.getCanonicalName() + "." + name);
    }

從代碼中能夠看到,代碼會嘗試從調用enumType這個Class對象的enumConstantDirectory()方法返回的map中獲取名字爲name的枚舉對象,若是不存在就會拋出異常。再進一步跟到enumConstantDirectory()方法,就會發現到最後會以反射的方式調用enumType這個類型的values()靜態方法,也就是上面咱們看到的編譯器爲咱們建立的那個方法,而後用返回結果填充enumType這個Class對象中的enumConstantDirectory屬性。因此,JVM對序列化有保證。

4. 枚舉可以避免反射攻擊,由於反射不支持建立枚舉對象

Constructor類的 newInstance方法中會判斷是否爲 enum,如果會拋出異常

@CallerSensitive
    public T newInstance(Object ... initargs) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
        if (!override) {
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                Class<?> caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, null, modifiers);
            }
        }
        // 不能爲 ENUM,不然拋出異常:不能經過反射建立 enum 對象
        if ((clazz.getModifiers() & Modifier.ENUM) != 0)
            throw new IllegalArgumentException("Cannot reflectively create enum objects");
        ConstructorAccessor ca = constructorAccessor;   // read volatile
        if (ca == null) {
            ca = acquireConstructorAccessor();
        }
        @SuppressWarnings("unchecked")
        T inst = (T) ca.newInstance(initargs);
        return inst;
    }

單例模式總結

單例模式做爲一種目標明確、結構簡單、理解容易的設計模式,在軟件開發中使用頻率至關高,在不少應用軟件和框架中都得以普遍應用。

單例模式的主要優勢

  • 單例模式提供了對惟一實例的受控訪問。
  • 因爲在系統內存中只存在一個對象,所以能夠節約系統資源,對於一些須要頻繁建立和銷燬的對象,單例模式能夠提升系統的性能。
  • 容許可變數目的實例。基於單例模式咱們能夠進行擴展,使用與單例控制類似的方法來得到指定個數的對象實例,既節省系統資源,又解決了單例單例對象共享過多有損性能的問題。

單例模式的主要缺點

  • 因爲單例模式中沒有抽象層,所以單例類的擴展有很大的困難。
  • 單例類的職責太重,在必定程度上違背了 "單一職責原則"。
  • 若是實例化的共享對象長時間不被利用,系統可能會認爲它是垃圾,會自動銷燬並回收資源,下次利用時又將從新實例化,這將致使共享的單例對象狀態的丟失。

適用場景

  • 系統只須要一個實例對象,如系統要求提供一個惟一的序列號生成器或資源管理器,或者須要考慮資源消耗太大而只容許建立一個對象。
  • 客戶調用類的單個實例只容許使用一個公共訪問點,除了該公共訪問點,不能經過其餘途徑訪問該實例。

單例模式的典型應用

JDK Runtime 餓漢單例

JDK Runtime類表明着Java程序的運行時環境,每一個Java程序都有一個Runtime實例,該類會被自動建立,咱們能夠經過 Runtime.getRuntime() 方法來獲取當前程序的Runtime實例。一旦獲得了一個當前的Runtime對象的引用,就能夠調用Runtime對象的方法去控制Java虛擬機的狀態和行爲。

Runtime 應用了餓漢式單例模式

public class Runtime {
    private static Runtime currentRuntime = new Runtime();

    public static Runtime getRuntime() {
        return currentRuntime;
    }

    private Runtime() {
    }
    //....
}

API 介紹

addShutdownHook(Thread hook) 註冊新的虛擬機來關閉掛鉤。 
availableProcessors() 向 Java 虛擬機返回可用處理器的數目。 
exec(String command) 在單獨的進程中執行指定的字符串命令。 
exec(String[] cmdarray) 在單獨的進程中執行指定命令和變量。 
exec(String[] cmdarray, String[] envp) 在指定環境的獨立進程中執行指定命令和變量。 
exec(String[] cmdarray, String[] envp, File dir) 在指定環境和工做目錄的獨立進程中執行指定的命令和變量。 
exec(String command, String[] envp) 在指定環境的單獨進程中執行指定的字符串命令。 
exec(String command, String[] envp, File dir) 在有指定環境和工做目錄的獨立進程中執行指定的字符串命令。 
exit(int status) 經過啓動虛擬機的關閉序列,終止當前正在運行的 Java 虛擬機。 
freeMemory() 返回 Java 虛擬機中的空閒內存量。 
gc() 運行垃圾回收器。  
getRuntime() 返回與當前 Java 應用程序相關的運行時對象。 
halt(int status) 強行終止目前正在運行的 Java 虛擬機。 
load(String filename) 加載做爲動態庫的指定文件名。 
loadLibrary(String libname) 加載具備指定庫名的動態庫。 
maxMemory() 返回 Java 虛擬機試圖使用的最大內存量。 
removeShutdownHook(Thread hook) 取消註冊某個先前已註冊的虛擬機關閉掛鉤。 
runFinalization() 運行掛起 finalization 的全部對象的終止方法。 
totalMemory() 返回 Java 虛擬機中的內存總量。 
traceInstructions(on) 啓用/禁用指令跟蹤。 
traceMethodCalls(on) 啓用/禁用方法調用跟蹤。

AWT Desktop 容器單例

Desktop 類容許 Java 應用程序啓動已在本機桌面上註冊的關聯應用程序,以處理 URI 或文件。支持的操做包括:

  • 打開瀏覽器: 啓動用戶默認瀏覽器來顯示指定的 URI;
  • 打開郵件客戶端: 啓動帶有可選 mailto URI 的用戶默認郵件客戶端;
  • 打開文件/文件夾: 啓動已註冊的應用程序,以打開、編輯 或 打印 指定的文件。

Desktop 經過一個容器來管理單例對象

public class Desktop {
    // synchronized 同步方法
    public static synchronized Desktop getDesktop(){
        if (GraphicsEnvironment.isHeadless()) throw new HeadlessException();
        if (!Desktop.isDesktopSupported()) {
            throw new UnsupportedOperationException("Desktop API is not " + "supported on the current platform");
        }
        sun.awt.AppContext context = sun.awt.AppContext.getAppContext();
        Desktop desktop = (Desktop)context.get(Desktop.class); // 獲取單例對象
        // 存在則返回,不存在則建立,建立後put進容器
        if (desktop == null) {
            desktop = new Desktop(); 
            context.put(Desktop.class, desktop);
        }
        return desktop;
    }

AppContext 中有一個 HashMap 對象table,是實際的容器對象

private final Map<Object, Object> table = new HashMap();

spring AbstractFactoryBean

AbstractFactoryBean 類

public final T getObject() throws Exception {
    if (this.isSingleton()) {
        return this.initialized ? this.singletonInstance : this.getEarlySingletonInstance();
    } else {
        return this.createInstance();
    }
}

private T getEarlySingletonInstance() throws Exception {
    Class<?>[] ifcs = this.getEarlySingletonInterfaces();
    if (ifcs == null) {
        throw new FactoryBeanNotInitializedException(this.getClass().getName() + " does not support circular references");
    } else {
        if (this.earlySingletonInstance == null) {
            // 經過代理建立對象
            this.earlySingletonInstance = Proxy.newProxyInstance(this.beanClassLoader, ifcs, new AbstractFactoryBean.EarlySingletonInvocationHandler());
        }
        return this.earlySingletonInstance;
    }
}

Mybatis ErrorContext ThreadLocal

ErrorContext 類,經過 ThreadLocal 管理單例對象,一個線程一個ErrorContext對象,ThreadLocal能夠保證線程安全

public class ErrorContext {
    private static final ThreadLocal<ErrorContext> LOCAL = new ThreadLocal<ErrorContext>();
    private ErrorContext() {
    }
    
    public static ErrorContext instance() {
        ErrorContext context = LOCAL.get();
        if (context == null) {
          context = new ErrorContext();
          LOCAL.set(context);
        }
        return context;
    }
    //...
}

參考:
http://www.hollischuang.com/archives/197
http://www.javashuo.com/article/p-ozksreer-gs.html
https://blog.csdn.net/abc123lzf/article/details/82318148

後記

歡迎評論、轉發、分享,您的支持是我最大的動力

更多內容可訪問個人我的博客:http://laijianfeng.org

關注【小旋鋒】微信公衆號,及時接收博文推送

關注_小旋鋒_微信公衆號

相關文章
相關標籤/搜索