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

1. 定義

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

2. 適用場景

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

3. 分類

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

4. 餓漢式單例

餓漢式單例是在類加載的時候就當即初始化,而且建立單例對象.絕對線程安全,在線程還沒出現之前就是實例化了,不可能存在訪問安全問題緩存

4.1 優/缺點

  • 優勢:沒有加任何的鎖,執行效率比較高,在用戶體驗上來講,比懶漢式更好
  • 缺點:類加載的時候就初始化,無論用與不用都佔着空間,浪費了內存,有可能佔着茅坑不拉屎

4.2 餓漢式單例分類

  • 直接經過 new 建立實例
  • 經過 static 模塊建立實例

4.2.1 直接經過 new 建立實例

public class HungrySingleton {

    private static final HungrySingleton hungrySingleton = new HungrySingleton();

    private HungrySingleton() {}

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

4.2.2 經過 static 模塊建立實例

public class HungryStaticSingleton {

    private static final HungryStaticSingleton hungrySingleton;

    private HungryStaticSingleton() {}

    static {
        hungrySingleton = new HungryStaticSingleton();
    }

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

5. 懶漢式單例

當類被外部調用時才建立實例安全

懶漢式單例分爲如下幾種:多線程

  • 簡單懶漢式單例
  • 雙重檢查懶漢式單例
  • 靜態內部類懶漢式單例

5.1 簡單懶漢式單例

  • 在併發場景下存在線程安全問題,能夠建立出多個對象
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 多線程調試

在 IDEA 中用線程模式調試,手動控制線程的執行順序來跟蹤內存的變化狀態,給 LazySimpleSingleton 加上斷點,選擇 Thread 模式併發

debug 啓動測試類LazySimpleSingletonTest,線程 0 和 1 進入斷點,在 debug 中將線程控制下拉框中切換至線程 0,執行下一行代碼後停住
app

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

輸出結果,LazySimpleSingleton 實例化兩個對象
工具

多線程類源碼分析

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("執行結束");
    }
}

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 多線程調試

在 IDEA 中用線程模式調試,手動控制線程的執行順序來跟蹤內存的變化狀態,給 LazyDoubleCheckSingleton 加上斷點,選擇 Thread 模式

debug 啓動測試類LazyDoubleCheckSingletonTest,線程 0 和 1 進入斷點,此時線程 0 和 1 都是 RUNNING

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

線程再切換至線程 1 執行下一行代碼,此時線程 0 是 RUNNING,線程 1 是 MONITOR,線程 0 拿到鎖的狀況下,線程 1 沒法進入建立實例代碼區域

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

執行結果,LazyDoubleCheckSingleton 類只被實例化了一次

5.3 靜態內部類懶漢式單例

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

    private LazyInnerClassSingleton(){}

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

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

5.4 反射破壞單例

5.4.1 反射如何破壞單例

以前的餓漢和懶漢單例模式的構造方法除了加上 private,若是咱們使用反射來調用其構造方法,而後再調用 getInstance()方法,應該就會兩個不一樣的實例

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

運行結果爲 false,object1 是經過反射建立的,object2 是一般正常建立的,這是兩個指向不一樣內存地址的對象,破壞了單例

5.4.2 解決反射破壞單例

在私有構造方法中增長判斷能防止反射破壞單例

private LazyInnerClassSingleton(){
    /**
     * 在私有構造方法中增長判斷能防止反射破壞單例
     */
    if (null != LazyHolder.LAZY) {
        throw new RuntimeException("禁止反射建立實例");
    }
}
/**
 * 靜態內部類懶漢式單例
 */
public class LazyInnerClassSingleton {

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

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

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

運行結果

5.5 序列化破壞單例

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

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

輸出結果爲兩個對象 s1,s2 不相等,SerializableSingleton 對象實例化了兩個對象,分別指向不一樣的內存地址

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 方法只不過是覆蓋了反序列化出來的對象
    // 仍是建立了兩次,只不過是發生在 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中調用readOrdinaryObject()

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

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

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

調用了 ObjectStreamClass 的 isInstantiable()方法,而 isInstantiable()裏面的代碼,判斷一下構造方法是否爲空,構造方法不爲空就返回 true,意味着只要有無參構造方法就會實例化

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

判斷無參構造方法是否存在以後,又調用了 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,將能執行desc.invokeReadResolve(obj)調用序列化對象中的 readResolve 方法

  • obj 已經實例化不爲空
  • handles.lookupException(passHandle)返回結果爲空,沒有異常
  • 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()方法中用反射調用了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()方法返回實例,解決了單例被破壞的問題.可是咱們經過分析源碼以及調試,咱們能夠看到實際上實例化了兩次,只不過新建立的對象沒有被返回而已.那若是建立對象的動做發生頻率增大,就意味着內存分配開銷也就隨之增大,難道真的就沒辦法從根本上解決問題嗎?下面咱們來註冊式單例也許能幫助到你

6. 註冊式單例

註冊式單例又稱爲登記式單例,就是將每個實例都登記到某一個地方,使用惟一的標識獲取實例

註冊式單例有兩種寫法:

  • 容器緩存
  • 枚舉登記

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

運行結果,兩個對象 s1,s2 指向同一塊內存地址,是同一個對象

這種方式沒有作任何處理卻能完美的解決序列化破壞單例,那麼枚舉式單例如此神奇,經過反編譯工具解開神祕面紗

在 IDEA 中找到 EnumSingleton 對應的 class 文件 EnumSingleton.class,複製所在路徑

而後切回到命令行,切換到工程所在的 Class 目錄,輸入命令 jad 後面輸入複製好的路徑,咱們會在 Class 目錄下會多一個 EnumSingleton.jad 文件.打開 EnumSingleton.jad 文件咱們驚奇又巧妙地發現有以下代碼:

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

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

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

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

在 readObject0()中調用了 readEnum()方法,來看 readEnum()中代碼實現

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

發現枚舉類型其實經過類名和 Class 對象類找到一個惟一的枚舉對象,所以枚舉對象不可能被類加載器加載屢次

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

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

運行結果

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

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

運行結果

這時錯誤已經很是明顯了,告訴咱們 Cannotreflectivelycreateenumobjects,不能用反射來建立枚舉類型.仍是習慣性地想來看看 JDK 源碼,進入 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 枚舉類型,直接拋出異常.到這爲止,咱們是否是已經很是清晰明瞭呢?枚舉式單例也是《EffectiveJava》書中推薦的一種單例實現寫法.在 JDK 枚舉的語法特殊性,以及反射也爲枚舉保駕護航,讓枚舉式單例成爲一種比較優雅的實現

6.2 容器緩存單例

容器式寫法適用於建立實例很是多的狀況,便於管理,是非線程安全的

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 中的容器式單例的實現代碼

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 不能保證其建立的對象是全局惟一,可是能保證在單個線程中是惟一的,天生的線程安全.下面咱們來看代碼:

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

多線程執行類

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

輸出結果

咱們發現,在主線程 main 中不管調用多少次,獲取到的實例都是同一個,都在兩個子線程中分別獲取到了不一樣的實例.那麼 ThreadLocal 是若是實現這樣的效果的呢?咱們知道上面的單例模式爲了達到線程安全的目的,給方法上鎖,以時間換空間.ThreadLocal 將全部的對象所有放在 ThreadLocalMap 中,爲每一個線程都提供一個對象,其實是以空間換時間來實現線程間隔離的

相關文章
相關標籤/搜索