[01][01][04] 單例模式詳解

[TOC]

1. 定義

指確保一個類在任何情況下都絕對只有一個實例,并提供一個全局訪問點

2. 適用場景

  • 確保任何情況下都絕對只有一個實例
  • ServletContext,ServletConfig,ApplicationContext,DBPool

3. 分類

  • 餓漢式單例
  • 懶漢式單例
  • 注冊式單例
  • ThreadLocal 單例

4. 餓漢式單例

餓漢式單例是在類加載的時候就立即初始化,并且創(chuàng)建單例對象.絕對線程安全,在線程還沒出現(xiàn)以前就是實例化了,不可能存在訪問安全問題

4.1 優(yōu)/缺點

  • 優(yōu)點:沒有加任何的鎖,執(zhí)行效率比較高,在用戶體驗上來說,比懶漢式更好
  • 缺點:類加載的時候就初始化,不管用與不用都占著空間,浪費了內(nèi)存,有可能占著茅坑不拉屎

4.2 餓漢式單例分類

  • 直接通過 new 創(chuàng)建實例
  • 通過 static 模塊創(chuàng)建實例

4.2.1 直接通過 new 創(chuàng)建實例

public class HungrySingleton {

    private static final HungrySingleton hungrySingleton = new HungrySingleton();

    private HungrySingleton() {}

    public static HungrySingleton getInstance() {
        return hungrySingleton;
    }
}

4.2.2 通過 static 模塊創(chuàng)建實例

public class HungryStaticSingleton {

    private static final HungryStaticSingleton hungrySingleton;

    private HungryStaticSingleton() {}

    static {
        hungrySingleton = new HungryStaticSingleton();
    }

    public static HungryStaticSingleton getInstance() {
        return hungrySingleton;
    }
}

5. 懶漢式單例

當(dāng)類被外部調(diào)用時才創(chuàng)建實例

懶漢式單例分為以下幾種:

  • 簡單懶漢式單例
  • 雙重檢查懶漢式單例
  • 靜態(tài)內(nèi)部類懶漢式單例

5.1 簡單懶漢式單例

  • 在并發(fā)場景下存在線程安全問題,可以創(chuàng)建出多個對象
public class LazySimpleSingleton {

    private static LazySimpleSingleton lazySimpleSingleton = null;

    private LazySimpleSingleton(){}

    public static LazySimpleSingleton getInstance() {
        if (null == lazySimpleSingleton) {
            lazySimpleSingleton = new LazySimpleSingleton();
        }
        return lazySimpleSingleton;
    }
}

5.1.1 多線程調(diào)試

在 IDEA 中用線程模式調(diào)試,手動控制線程的執(zhí)行順序來跟蹤內(nèi)存的變化狀態(tài),給 LazySimpleSingleton 加上斷點,選擇 Thread 模式

image

debug 啟動測試類LazySimpleSingletonTest,線程 0 和 1 進(jìn)入斷點,在 debug 中將線程控制下拉框中切換至線程 0,執(zhí)行下一行代碼后停住

image

切換至線程 1 執(zhí)行下一行代碼后停住,LazySimpleSingleton類被實例化了兩次,這樣就破壞了單例模式

image

輸出結(jié)果,LazySimpleSingleton 實例化兩個對象


image

多線程類

public class ExecutorThread implements Runnable {
    @Override
    public void run() {
        LazySimpleSingleton lazySimpleSingleton = LazySimpleSingleton.getInstance();
        System.out.println(Thread.currentThread().getName() + ":" + lazySimpleSingleton);
    }
}

多線程測試類

public class LazySimpleSingletonTest {

    public static void main(String[] args) {
        Thread thread1 = new Thread(new ExecutorThread());

        Thread thread2 = new Thread(new ExecutorThread());

        thread1.start();
        thread2.start();

        System.out.println("執(zhí)行結(jié)束");
    }
}

5.2 雙重檢查懶漢式單例

  • 通過 synchronized 和雙重檢查,解決簡單懶漢式單例存在的線程安全問題
public class LazyDoubleCheckSingleton {

    private static LazyDoubleCheckSingleton lazyDoubleCheckSingleton = null;

    private LazyDoubleCheckSingleton(){}

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

5.2.1 多線程調(diào)試

在 IDEA 中用線程模式調(diào)試,手動控制線程的執(zhí)行順序來跟蹤內(nèi)存的變化狀態(tài),給 LazyDoubleCheckSingleton 加上斷點,選擇 Thread 模式


image

debug 啟動測試類LazyDoubleCheckSingletonTest,線程 0 和 1 進(jìn)入斷點,此時線程 0 和 1 都是 RUNNING

image

在 debug 中將線程控制下拉框中切換至線程 0,執(zhí)行下一行代碼后停住,此時線程 0 和 1 都是 RUNNING


image

線程再切換至線程 1 執(zhí)行下一行代碼,此時線程 0 是 RUNNING,線程 1 是 MONITOR,線程 0 拿到鎖的情況下,線程 1 無法進(jìn)入創(chuàng)建實例代碼區(qū)域


image

直到線程 0 執(zhí)行完釋放鎖線程 1 才能執(zhí)行代碼,此時線程 0 和 1 都是 RUNNING,lazyDoubleCheckSingleton 對象已經(jīng)創(chuàng)建,if(null==lazyDoubleCheckSingleton)因條件不成立而不進(jìn)行實例創(chuàng)建,這樣線程就是安全的

image

執(zhí)行結(jié)果,LazyDoubleCheckSingleton 類只被實例化了一次


image

5.3 靜態(tài)內(nèi)部類懶漢式單例

  • 全程沒有用到 synchronized
  • 巧妙利用了內(nèi)部類的特性
  • JVM 底層執(zhí)行邏輯,完美的避免了線程安全問題
  • 存在被反射攻擊的風(fēng)險,通過if(null!=LazyHolder.LAZY)解決反射問題
/**
 * 靜態(tài)內(nèi)部類懶漢式單例
 */
public class LazyInnerClassSingleton {

    private LazyInnerClassSingleton(){}

    // LazyHolder 里面的邏輯需要等到外部方法調(diào)用時才執(zhí)行
    // 全程沒有用到 synchronized
    // 巧妙利用了內(nèi)部類的特性
    // JVM 底層執(zhí)行邏輯,完美的避免了線程安全問題
    public static LazyInnerClassSingleton getInstance() {
        return LazyHolder.LAZY;
    }

    private static class LazyHolder {
        private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
    }
}

5.4 反射破壞單例

5.4.1 反射如何破壞單例

之前的餓漢和懶漢單例模式的構(gòu)造方法除了加上 private,如果我們使用反射來調(diào)用其構(gòu)造方法,然后再調(diào)用 getInstance()方法,應(yīng)該就會兩個不同的實例

public class LazyInnerClassSingletonTest {

    public static void main(String[] args) throws NoSuchMethodException {
        try {
            Class<?> clazz = LazyInnerClassSingleton.class;

            Constructor constructor = clazz.getDeclaredConstructor(null);
            constructor.setAccessible(true);

            Object object1 = constructor.newInstance();

            Object object2 = LazyInnerClassSingleton.getInstance();

            System.out.println(object1 == object2);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

運行結(jié)果為 false,object1 是通過反射創(chuàng)建的,object2 是通常正常創(chuàng)建的,這是兩個指向不同內(nèi)存地址的對象,破壞了單例

5.4.2 解決反射破壞單例

在私有構(gòu)造方法中增加判斷能防止反射破壞單例

private LazyInnerClassSingleton(){
    /**
     * 在私有構(gòu)造方法中增加判斷能防止反射破壞單例
     */
    if (null != LazyHolder.LAZY) {
        throw new RuntimeException("禁止反射創(chuàng)建實例");
    }
}
/**
 * 靜態(tài)內(nèi)部類懶漢式單例
 */
public class LazyInnerClassSingleton {

    private LazyInnerClassSingleton(){
        /**
         * 在私有構(gòu)造方法中增加判斷能防止反射破壞單例
         */
        if (null != LazyHolder.LAZY) {
            throw new RuntimeException("禁止反射創(chuàng)建實例");
        }
    }

    // LazyHolder 里面的邏輯需要等到外部方法調(diào)用時才執(zhí)行
    // 全程沒有用到 synchronized
    // 巧妙利用了內(nèi)部類的特性
    // JVM 底層執(zhí)行邏輯,完美的避免了線程安全問題
    public static LazyInnerClassSingleton getInstance() {
        return LazyHolder.LAZY;
    }

    private static class LazyHolder {
        private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
    }
}

運行結(jié)果


image

5.5 序列化破壞單例

通過序列化將對象輸出到文件,再通過反序列化將文件加載到內(nèi)存,這樣單例實例將在內(nèi)存中存在兩個對象,從而破壞單例模式

5.5.1 序列化如何破壞單例

餓漢模式的單例

public class SerializableSingleton implements Serializable {
    private static final SerializableSingleton serializableSingleton = new SerializableSingleton();

    private SerializableSingleton() {}

    public static SerializableSingleton getInstance() {
        return serializableSingleton;
    }
}

序列化測試類

public class SerializableSingletonTest {
    public static void main(String[] args) {
        SerializableSingleton s1 = null;
        SerializableSingleton s2 = SerializableSingleton.getInstance();

        FileOutputStream fileOutputStream = null;
        FileInputStream fileInputStream = null;
        ObjectOutputStream objectOutputStream = null;
        ObjectInputStream objectInputStream = null;

        try {
            fileOutputStream = new FileOutputStream("./singleton/SerializableSingleton.obj");
            objectOutputStream = new ObjectOutputStream(fileOutputStream);
            objectOutputStream.writeObject(s2);
            objectOutputStream.flush();
            objectOutputStream.close();

            fileInputStream = new FileInputStream("./singleton/SerializableSingleton.obj");
            objectInputStream = new ObjectInputStream(fileInputStream);
            s1 = (SerializableSingleton) objectInputStream.readObject();
            objectInputStream.close();

            System.out.println(s1 == s2);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            IOUtils.closeQuietly(objectInputStream);
            IOUtils.closeQuietly(objectOutputStream);
            IOUtils.closeQuietly(fileInputStream);
            IOUtils.closeQuietly(fileOutputStream);
        }
    }
}

輸出結(jié)果為兩個對象 s1,s2 不相等,SerializableSingleton 對象實例化了兩個對象,分別指向不同的內(nèi)存地址

5.5.2 解決序列化破壞單例模式

在單例類中重寫readResolve()方法

public class SerializableSingleton implements Serializable {
    private static final SerializableSingleton serializableSingleton = new SerializableSingleton();

    private SerializableSingleton() {}

    public static SerializableSingleton getInstance() {
        return serializableSingleton;
    }

    // 重寫 readResolve 方法只不過是覆蓋了反序列化出來的對象
    // 還是創(chuàng)建了兩次,只不過是發(fā)生在 JVM 層面,相對來說說比較安全
    // 之前反序列化出來的對象會被 GC 回收
    private Object readResolve() {
        return serializableSingleton;
    }
}

5.5.3 源碼解讀

在序列化測試類的代碼中s1=(SerializableSingleton)objectInputStream.readObject(),查看readObject()方法源碼

public final Object readObject() throws IOException, ClassNotFoundException
{
    if (enableOverride) {
        return readObjectOverride();
    }

    // if nested read, passHandle contains handle of enclosing object
    int outerHandle = passHandle;
    try {
        Object obj = readObject0(false);
        handles.markDependency(outerHandle, passHandle);
        ClassNotFoundException ex = handles.lookupException(passHandle);
        if (ex != null) {
            throw ex;
        }
        if (depth == 0) {
            vlist.doCallbacks();
        }
        return obj;
    } finally {
        passHandle = outerHandle;
        if (closed && depth == 0) {
            clear();
        }
    }
}

readObject()源碼中可以查看到readObject0()方法,在TC_OBJECT中調(diào)用readOrdinaryObject()

private Object readObject0(boolean unshared) throws IOException {
    ...
    case TC_OBJECT:
        return checkResolve(readOrdinaryObject(unshared));
    ...
}

readOrdinaryObject()方法中desc.isInstantiable()判斷是否存在構(gòu)造方法

private Object readOrdinaryObject(boolean unshared) throws IOException
{
    ...
    Object obj;
    try {
        obj = desc.isInstantiable() ? desc.newInstance() : null;
    } catch (Exception ex) {

    ...

    if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod())
    {
        Object rep = desc.invokeReadResolve(obj);
        if (unshared && rep.getClass().isArray()) {
            rep = cloneArray(rep);
        }
        if (rep != obj) {
            // Filter the replacement object
            if (rep != null) {
                if (rep.getClass().isArray()) {
                    filterCheck(rep.getClass(), Array.getLength(rep));
                } else {
                    filterCheck(rep.getClass(), -1);
                }
            }
            handles.setObject(passHandle, obj = rep);
        }
    }
}

調(diào)用了 ObjectStreamClass 的 isInstantiable()方法,而 isInstantiable()里面的代碼,判斷一下構(gòu)造方法是否為空,構(gòu)造方法不為空就返回 true,意味著只要有無參構(gòu)造方法就會實例化

boolean isInstantiable() {
    requireInitialized();
    return (cons != null);
}

判斷無參構(gòu)造方法是否存在之后,又調(diào)用了 hasReadResolveMethod()方法

boolean hasReadResolveMethod() {
    requireInitialized();
    return (readResolveMethod != null);
}

就是判斷 readResolveMethod 是否為空,不為空就返回 true,通過全局查找找到了 ObjectStreamClass 中對 readResolveMethod 賦值代碼在私有方法

readResolveMethod = getInheritableMethod(cl, "readResolve", null, Object.class);

在代碼可以看到這樣一行代碼if(obj!=null&&handles.lookupException(passHandle)==null&&desc.hasReadResolveMethod()),如果這個判斷返回為 true,將能執(zhí)行desc.invokeReadResolve(obj)調(diào)用序列化對象中的 readResolve 方法

  • obj 已經(jīng)實例化不為空
  • handles.lookupException(passHandle)返回結(jié)果為空,沒有異常
  • desc.hasReadResolveMethod()返回 true
private Object readOrdinaryObject(boolean unshared) throws IOException
{
    ...

    if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod())
    {
        Object rep = desc.invokeReadResolve(obj);
        if (unshared && rep.getClass().isArray()) {
            rep = cloneArray(rep);
        }
        if (rep != obj) {
            // Filter the replacement object
            if (rep != null) {
                if (rep.getClass().isArray()) {
                    filterCheck(rep.getClass(), Array.getLength(rep));
                } else {
                    filterCheck(rep.getClass(), -1);
                }
            }
            handles.setObject(passHandle, obj = rep);
        }
    }
}

invokeReadResolve()方法中用反射調(diào)用了readResolveMethod方法

Object invokeReadResolve(Object obj) throws IOException, UnsupportedOperationException
{
    requireInitialized();
    if (readResolveMethod != null) {
        try {
            return readResolveMethod.invoke(obj, (Object[]) null);
        } catch (InvocationTargetException ex) {
            Throwable th = ex.getTargetException();
            if (th instanceof ObjectStreamException) {
                throw (ObjectStreamException) th;
            } else {
                throwMiscException(th);
                throw new InternalError(th);  // never reached
            }
        } catch (IllegalAccessException ex) {
            // should not occur, as access checks have been suppressed
            throw new InternalError(ex);
        }
    } else {
        throw new UnsupportedOperationException();
    }
}

通過 JDK 源碼分析我們可以看出,雖然增加readResolve()方法返回實例,解決了單例被破壞的問題.但是我們通過分析源碼以及調(diào)試,我們可以看到實際上實例化了兩次,只不過新創(chuàng)建的對象沒有被返回而已.那如果創(chuàng)建對象的動作發(fā)生頻率增大,就意味著內(nèi)存分配開銷也就隨之增大,難道真的就沒辦法從根本上解決問題嗎?下面我們來注冊式單例也許能幫助到你

6. 注冊式單例

注冊式單例又稱為登記式單例,就是將每一個實例都登記到某一個地方,使用唯一的標(biāo)識獲取實例

注冊式單例有兩種寫法:

  • 容器緩存
  • 枚舉登記

6.1 枚舉登記式單例

枚舉單例類

public enum EnumSingleton {
    INSTANCE;

    private Object data;

    public void setData(Object data) {
        this.data = data;
    }

    public Object getData() {
        return data;
    }

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

反序列化測試類

public class EnumSingletonTest {
    public static void main(String[] args) {
        EnumSingleton s1 = null;
        EnumSingleton s2 = EnumSingleton.getInstance();

        FileOutputStream fileOutputStream = null;
        FileInputStream fileInputStream = null;
        ObjectOutputStream objectOutputStream = null;
        ObjectInputStream objectInputStream = null;

        try {
            fileOutputStream = new FileOutputStream("./singleton/EnumSingleton.obj");
            objectOutputStream = new ObjectOutputStream(fileOutputStream);
            objectOutputStream.writeObject(s2);
            objectOutputStream.flush();
            objectOutputStream.close();

            fileInputStream = new FileInputStream("./singleton/EnumSingleton.obj");
            objectInputStream = new ObjectInputStream(fileInputStream);
            s1 = (EnumSingleton) objectInputStream.readObject();
            objectInputStream.close();

            System.out.println(s1 == s2);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            IOUtils.closeQuietly(objectInputStream);
            IOUtils.closeQuietly(objectOutputStream);
            IOUtils.closeQuietly(fileInputStream);
            IOUtils.closeQuietly(fileOutputStream);
        }
    }
}

運行結(jié)果,兩個對象 s1,s2 指向同一塊內(nèi)存地址,是同一個對象


image

這種方式?jīng)]有做任何處理卻能完美的解決序列化破壞單例,那么枚舉式單例如此神奇,通過反編譯工具解開神秘面紗

在 IDEA 中找到 EnumSingleton 對應(yīng)的 class 文件 EnumSingleton.class,復(fù)制所在路徑


image

然后切回到命令行,切換到工程所在的 Class 目錄,輸入命令 jad 后面輸入復(fù)制好的路徑,我們會在 Class 目錄下會多一個 EnumSingleton.jad 文件.打開 EnumSingleton.jad 文件我們驚奇又巧妙地發(fā)現(xiàn)有如下代碼:

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

原來枚舉式單例在靜態(tài)代碼塊中就給 INSTANCE 進(jìn)行了賦值,是餓漢式單例的實現(xiàn).我們還可以試想,序列化我們能否破壞枚舉式單例呢?我們不妨再來看一下 JDK 源碼,還是回到 ObjectInputStream 的 readObject0()方法

private Object readObject0(boolean unshared) throws IOException {
    ...

    case TC_ENUM:
        return checkResolve(readEnum(unshared));
    ...
}

在 readObject0()中調(diào)用了 readEnum()方法,來看 readEnum()中代碼實現(xiàn)

private Enum<?> readEnum(boolean unshared) throws IOException {
    if (bin.readByte() != TC_ENUM) {
        throw new InternalError();
    }

    ObjectStreamClass desc = readClassDesc(false);
    if (!desc.isEnum()) {
        throw new InvalidClassException("non-enum class: " + desc);
    }

    int enumHandle = handles.assign(unshared ? unsharedMarker : null);
    ClassNotFoundException resolveEx = desc.getResolveException();
    if (resolveEx != null) {
        handles.markException(enumHandle, resolveEx);
    }

    String name = readString(false);
    Enum<?> result = null;
    Class<?> cl = desc.forClass();
    if (cl != null) {
        try {
            @SuppressWarnings("unchecked")
            Enum<?> en = Enum.valueOf((Class)cl, name);
            result = en;
        } catch (IllegalArgumentException ex) {
            throw (IOException) new InvalidObjectException(
                "enum constant " + name + " does not exist in " +
                cl).initCause(ex);
        }
        if (!unshared) {
            handles.setObject(enumHandle, result);
        }
    }

    handles.finish(enumHandle);
    passHandle = enumHandle;
    return result;
}

發(fā)現(xiàn)枚舉類型其實通過類名和 Class 對象類找到一個唯一的枚舉對象,因此枚舉對象不可能被類加載器加載多次

那么反射是否能破壞枚舉式單例呢?來看一段測試代碼:

public static void main(String[] args) {
    try {
        Class clazz = EnumSingleton.class;
        Constructor c = clazz.getDeclaredConstructor();
        c.newInstance();
    }catch (Exception e){
        e.printStackTrace();
    }
}

運行結(jié)果


image

報的是 java.lang.NoSuchMethodException 異常,意思是沒找到無參的構(gòu)造方法.這時候,我們打開 java.lang.Enum 的源碼代碼,查看它的構(gòu)造方法,只有一個 protected 的構(gòu)造方法,代碼如下:

protected Enum(String name, int ordinal) {
    this.name = name;
    this.ordinal = ordinal;
}

那我們再來做一個這樣的測試:

public static void main(String[] args) {
    try {
        Class clazz = EnumSingleton.class;
        Constructor c = clazz.getDeclaredConstructor(String.class,int.class);
        c.setAccessible(true);
        EnumSingleton enumSingleton = (EnumSingleton)c.newInstance("Tom",666);
    }catch (Exception e){
        e.printStackTrace();
    }
}

運行結(jié)果


image

這時錯誤已經(jīng)非常明顯了,告訴我們 Cannotreflectivelycreateenumobjects,不能用反射來創(chuàng)建枚舉類型.還是習(xí)慣性地想來看看 JDK 源碼,進(jìn)入 Constructor 的 newInstance()方法:

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);
        }
    }

    if ((clazz.getModifiers() & Modifier.ENUM) != 0) {
        throw new IllegalArgumentException("Cannot reflectively create enum objects");
    }

    ConstructorAccessor ca = constructorAccessor;

    if (ca == null) {
        ca = acquireConstructorAccessor();
    }

    @SuppressWarnings("unchecked")
    T inst = (T) ca.newInstance(initargs);

    return inst;
}

在 newInstance()方法中做了強制性的判斷,如果修飾符是 Modifier.ENUM 枚舉類型,直接拋出異常.到這為止,我們是不是已經(jīng)非常清晰明了呢?枚舉式單例也是《EffectiveJava》書中推薦的一種單例實現(xiàn)寫法.在 JDK 枚舉的語法特殊性,以及反射也為枚舉保駕護(hù)航,讓枚舉式單例成為一種比較優(yōu)雅的實現(xiàn)

6.2 容器緩存單例

容器式寫法適用于創(chuàng)建實例非常多的情況,便于管理,是非線程安全的

public class ContainerSingleton {

    private ContainerSingleton(){}

    private static Map<String,Object> ioc = new ConcurrentHashMap<String,Object>();

    public static Object getBean(String className){
        synchronized (ioc) {
            if (!ioc.containsKey(className)) {
                Object obj = null;

                try {
                    obj = Class.forName(className).newInstance();
                    ioc.put(className, obj);
                } catch (Exception e) {
                    e.printStackTrace();
                }

                return obj;
            } else {
                return ioc.get(className);
            }
        }
    }
}

Spring 中的容器式單例的實現(xiàn)代碼

public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory {
    /** Cache of unfinished FactoryBean instances: FactoryBean name --> BeanWrapper */
    private final Map<String, BeanWrapper> factoryBeanInstanceCache = new ConcurrentHashMap<>(16);
    ...
}

7. ThreadLocal 單例

ThreadLocal 不能保證其創(chuàng)建的對象是全局唯一,但是能保證在單個線程中是唯一的,天生的線程安全.下面我們來看代碼:

public class ThreadLocalSingleton {

    private static final ThreadLocal<ThreadLocalSingleton> threadLocalInstance =
            new ThreadLocal<ThreadLocalSingleton>() {
                @Override
                protected ThreadLocalSingleton initialValue() {
                    return new ThreadLocalSingleton();
                }
            };

    private ThreadLocalSingleton() {
    }

    public static ThreadLocalSingleton getInstance() {
        return threadLocalInstance.get();
    }
}

多線程執(zhí)行類

public class ThreadLocalExectorThread implements Runnable {

    @Override
    public void run() {
        ThreadLocalSingleton threadLocalSingleton = ThreadLocalSingleton.getInstance();

        System.out.println(Thread.currentThread().getName() + ":" + threadLocalSingleton);
    }
}

測試類

public class ThreadLocalSingletonTest {

    public static void main(String[] args) {
        System.out.println(ThreadLocalSingleton.getInstance());
        System.out.println(ThreadLocalSingleton.getInstance());
        System.out.println(ThreadLocalSingleton.getInstance());
        System.out.println(ThreadLocalSingleton.getInstance());
        System.out.println(ThreadLocalSingleton.getInstance());
        Thread t1 = new Thread(new ThreadLocalExectorThread());
        Thread t2 = new Thread(new ThreadLocalExectorThread());
        t1.start();
        t2.start();
    }
}

輸出結(jié)果


image

我們發(fā)現(xiàn),在主線程 main 中無論調(diào)用多少次,獲取到的實例都是同一個,都在兩個子線程中分別獲取到了不同的實例.那么 ThreadLocal 是如果實現(xiàn)這樣的效果的呢?我們知道上面的單例模式為了達(dá)到線程安全的目的,給方法上鎖,以時間換空間.ThreadLocal 將所有的對象全部放在 ThreadLocalMap 中,為每個線程都提供一個對象,實際上是以空間換時間來實現(xiàn)線程間隔離的

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容