[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 模式

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

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

輸出結(jié)果,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("執(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 模式

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

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

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

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

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

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é)果

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)存地址,是同一個對象

這種方式?jīng)]有做任何處理卻能完美的解決序列化破壞單例,那么枚舉式單例如此神奇,通過反編譯工具解開神秘面紗
在 IDEA 中找到 EnumSingleton 對應(yīng)的 class 文件 EnumSingleton.class,復(fù)制所在路徑

然后切回到命令行,切換到工程所在的 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é)果

報的是 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é)果

這時錯誤已經(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é)果

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