前言
接上文《Netty源码解析之线程池的实现(二):创建线程与执行任务》,我们知道NioEventLoopGroup在执行任务时,会调用ThreadPerTaskExecutor的execute方法。ThreadPerTaskExecutor中含有一个线程工厂threadFactory,在其execute方法中,会使用线程工厂threadFactory创建一个新线程。这个threadFactory在NioEventLoopGroup的构造方法中创建,为DefaultThreadFactory实例。
然后,在DefaultThreadFactory的newThread方法中,却有一些不一样的东西。
public Thread newThread(Runnable r) {
//1、将Runnable任务封装成FastThreadLocalRunnable
//2、拼接线程名称
//3、调用同名的newThread方法
Thread t = newThread(FastThreadLocalRunnable.wrap(r), prefix + nextId.incrementAndGet());
/*省略与本文内容无关的代码*/
return t;
}
//创建一个线程实例,FastThreadLocalThread
protected Thread newThread(Runnable r, String name) {
return new FastThreadLocalThread(threadGroup, r, name);
}
DefaultThreadFactory的newThread方法中,首先创建的是FastThreadLocalThread实例,而不是我们平常使用的Thread,其次使用FastThreadLocalRunnable.wrap®对需要执行的任务进行包装。Netty为什么要这么做?这是本文的内容。
JDK原生ThreadLocal的原理与缺陷
Netty中的FastThreadLocalThread实际上是配合Netty自定义的FastThreadLocal使用的线程,Netty之所以要自定义一个线程本地存储使用的FastThreadLocal,是因为JDK原生的ThreadLocal存在诸多不足。简而言之,Netty认为ThreadLocal不好,要改进它,这个改进版就是FastThreadLocal。
ThreadLocal的原理概括
关于ThreadLocal的实现原理,网上有很多长篇大论的文章,有些说的云里雾里的。我认为ThreadLocal的原理很简单,简而言之就5句话。
1、ThreadLocal的作用是创建一个线程本地存储的变量,让每个线程都保存一份变量的副本。因此在线程Thread类中就必须要有一个容器用来保存自己的副本,这个容器就是Thread类的threadLocals属性。在我们没有使用它时,它的默认值是null。
Thread源码中的threadLocals属性:
ThreadLocal.ThreadLocalMap threadLocals = null;
2、这个ThreadLocalMap里面包含一个哈希表table,用来存储每个ThreadLocal变量的副本。
3、ThreadLocal里面有个属性threadLocalHashCode,用来保存自己的哈希码,这个哈希码用来和哈希表的长度进行取模计算得到ThreadLocal变量的副本在哈希表table中的位置。
ThreadLocal源码中的threadLocalHashCode 属性:
private final int threadLocalHashCode = nextHashCode();
4、既然每个线程使用哈希表来保存ThreadLocal变量的副本,那么肯定会有哈希冲突的问题。即当两个ThreadLocal变量的threadLocalHashCode经过取模计算后,得到的在哈希表table中的位置相同。ThreadLocal使用线性探测法解决哈希冲突的问题,当某个ThreadLocal变量取模后得到的table的位置上有数据的时候,ThreadLocal采取的是办法是找最近的一个空的位置设置数据。
见ThreadLocal内部类ThreadLocalMap的set方法源码:
/*
* 保存ThreadLocal变量到哈希表中
* key:ThreadLocal实例
* value:ThreadLocal变量
*/
private void set(ThreadLocal<?> key, Object value) {
//用于保存变量的哈希表
Entry[] tab = table;
//哈希表的长度
int len = tab.length;
//使用threadLocalHashCode 进行取模
//得到变量在哈希表中的位置i
int i = key.threadLocalHashCode & (len-1);
//在哈希表中从第i个位置开始,寻找空位置插入
//每次循环,都使用nextIndex(i, len)方法获取下一个位置
//循环结束的条件是e == null,即在哈希表中找到了空位置
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
//取出哈希表当前位置存储的数据
ThreadLocal<?> k = e.get();
//若是Entry已经存在并且key等于传入的key,
//那么这时候直接给这个Entry赋新的value值
if (k == key) {
e.value = value;
return;
}
//若是Entry存在,但是key为null,
//则调用replaceStaleEntry来更换这个key为空的Entry
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
//走到这里,说明在哈希表中找到了空位置i
//将变量插入
tab[i] = new Entry(key, value);
//更新sz值
int sz = ++size;
//省略无关代码
}
5、ThreadLocal使用线性探测法解决哈希冲突的问题,那么在获取某线程的ThreadLocal变量时,使用threadLocalHashCode经过取模计算后,得到的在哈希表table中的位置就不一定准确。这个时候就要拿ThreadLocal实例去比对。
因此在哈希表中,就必须定义一个Entry来既保存ThreadLocal变量,也保存ThreadLocal实例。实际上,Entry使用了弱引用来存储ThreadLocal实例。
见ThreadLocal内部类ThreadLocalMap的Entry类的源码:
//WeakReference为弱引用
static class Entry extends WeakReference<ThreadLocal<?>> {
//ThreadLocal变量的值
Object value;
//Entry构造方法,传入ThreadLocal实例k和ThreadLocal变量变量v
Entry(ThreadLocal<?> k, Object v) {
//调用弱引用WeakReference的构造方法,存储ThreadLocal实例k
super(k);
value = v;
}
}
以上是ThreadLocal的实现原理,下面从网上盗个图,描述下ThreadLocal的数据结构。
题外话:ThreadLocal为什么要用弱引用?
关于ThreadLocal为什么要用弱引用这个问题众说纷纭,我认为在其源码的注释中给出了合理解释。
源码注释:
The entries in this hash map extend WeakReference, using its main ref field as the key (which is always a ThreadLocal object). Note that null keys (i.e. entry.get() == null) mean that the key is no longer referenced, so the entry can be expunged from table. Such entries are referred to as “stale entries” in the code that follows.
翻译过来是:
这个entries在这个哈希映射(指的是ThreadLocalMap)中继承了WeakReference,使用其主ref字段作为key(始终是ThreadLocal对象)。**注意null值的键(即i.e. entry.get() == null)表示key不再被引用,所以关联的entry可以从表中删除。**在下面的代码中,这些entries被称为“过期entries”。
重点看这一句话:注意null值的键(即i.e. entry.get() == null)表示key不再被引用,所以关联的entry可以从表中删除。
也就是说,ThreadLocal在判断哪些entry过期需要回收时,是根据entry.get() == null来判断的。entry.get()返回的就是弱引用的ThreadLocal实例。即当我们在代码中将ThreadLocal实例手动置为null了,但是没有调用ThreadLocal的remove方法将对应的entry从线程中删除。那么如果entry对ThreadLocal实例是强引用,则ThreadLocal实例无法被垃圾回收。这样的话,就没有办法去检查哈希表中哪些entry是过期需要删除的了。
因此entry对ThreadLocal实例设计了弱引用,如果某个ThreadLocal实例被手动置为null,那么弱引用不影响其垃圾回收。当ThreadLocal实例被垃圾回收以后,entry.get()方法通过弱引用来获取它时,返回的结果就是null,这时候就知道entry已经过期需要清理。
详情见ThreadLocal源码中遍历哈希表并清理掉过期的Entry的cleanSomeSlots。
private boolean cleanSomeSlots(int i, int n) {
boolean removed = false;
//转存哈希表
Entry[] tab = table;
//哈希表的长度
int len = tab.length;
do {
//循环获取哈希表的下一个位置
i = nextIndex(i, len);
//当前位置上的entry
Entry e = tab[i];
//如果当前位置上面有entry,但是entry弱引用的
//ThreadLocal实例已经不存在,则需要将其删除
if (e != null && e.get() == null) {
n = len;
removed = true;
i = expungeStaleEntry(i);
}
} while ( (n >>>= 1) != 0);
return removed;
}
ThreadLocal的局限性
1、使用线性探测方法解决哈希冲突问题性能较低。
2、内存泄漏问题。
在使用ThreadLocal时,如果我们在用完后手动将ThreadLocal实例置为null,并且没有在每个用到其的线程上调用ThreadLocal的remove方法将本线程中的副本删除,则就会导致ThreadLocal实例被垃圾回收了,但是每个线程中的副本一直都是被线程的ThreadLocalMap中的哈希表强引用,导致无法垃圾回收,直到线程对象也被垃圾回收为止。
FastThreadLocal的横空出世
FastThreadLocal解决问题的思路
对于ThreadLocal的缺陷,FastThreadLocal的解决方式是:
1、哈希冲突问题
用数组取代哈希表,并且使用一个全局的AtomicInteger型的变量采用自增的方式为每个FastThreadLocal实例分配索引下标。每当创建一个新的FastThreadLocal实例时,在其构造方法中,使用AtomicInteger的getAndIncrement()方法为其分配一个索引下标。这样就解决了哈希冲突的问题。
2、内存泄漏问题。
之前的ThreadLocal在每个线程使用结束后都需要手动调用remove方法将线程本地保存的副本清除,不然只要Thread对象不被回收,ThreadLocal变量的副本就会一直保存在线程的ThreadLocalMap中。
FastThreadLocal解决此问题的方式很简单,在线程执行完任务之后,自动调用FastThreadLocal的removeAll方法将当前线程保存的FastThreadLocal型的副本全部清除。
FastThreadLocal的源码与实现原理
FastThreadLocal需要结合FastThreadLocalThread使用
上文说到,FastThreadLocal使用数组取代哈希表来保存线程本地的变量副本,JDK原生的线程类Thread自然无法修改,因此只能只能重新定义一个线程类,在里面添加一个数组类型的属性。这个线程类就是FastThreadLocalThread,里面用来存储的本地变量副本的数组是InternalThreadLocalMap类中的indexedVariables属性。
看源码,FastThreadLocalThread继承于Thread,因此是个线程类,里面含有InternalThreadLocalMap类型的属性threadLocalMap。
//FastThreadLocalThread 是Thread 的子类
public class FastThreadLocalThread extends Thread {
//里面包含一个InternalThreadLocalMap类的属性threadLocalMap
private InternalThreadLocalMap threadLocalMap;
/* 无关代码不在此展示 */
}
InternalThreadLocalMap里面有一个数组indexedVariables,用来存储线程本地的变量副本。
public final class InternalThreadLocalMap extends UnpaddedInternalThreadLocalMap {
//用来存储线程本地的变量副本的数组
private Object[] indexedVariables;
/* 无关代码不在此展示 */
}
FastThreadLocal在实例化时分配索引下标
在FastThreadLocal唯一的构造方法中,里面只做了一件事,就是为当前创建的FastThreadLocal实例分配一个索引下标。
public FastThreadLocal() {
//获取本FastThreadLocal对象在数组中的索引下标
index = InternalThreadLocalMap.nextVariableIndex();
}
下面走到InternalThreadLocalMap.nextVariableIndex()源码中:
public static int nextVariableIndex() {
//返回当前值并且自增
int index = nextIndex.getAndIncrement();
//数组下标合法性检查,不得大于最大值,也不得小于0
if (index >= ARRAY_LIST_CAPACITY_MAX_SIZE || index < 0) {
//nextIndex的值不得超过Integer.MAX_VALUE - 8,否则有OutOfMemoryError风险
//为什么会这样,请读者自行了解
nextIndex.set(ARRAY_LIST_CAPACITY_MAX_SIZE);
throw new IllegalStateException("too many thread-local indexed variables");
}
return index;
}
其中的nextIndex是一个初始值为0的AtomicInteger类型的原子变量,每次调用nextVariableIndex()时都会自增一次。这里需要注意的是FastThreadLocal的索引下标是从1开始分配的,即第一个创建的FastThreadLocal实例的索引下标是1。为什么nextIndex的初始值0不用做FastThreadLocal实例的索引下标,下文会分析。
明确两个属性:threadLocalMap和threadLocals
结合上文,这两个属性需要留意,FastThreadLocal源码中经常用到:
threadLocals是Thread类中的属性,属于ThreadLocal.ThreadLocalMap类型,里面有个哈希表table用来保存ThreadLocal变量的副本。
threadLocalMap是FastThreadLocalThread中的属性,属于InternalThreadLocalMap类型,里面有个数组,用来保存FastThreadLocal变量的副本。
FastThreadLocal的set()方法
下面走进FastThreadLocal的set()方法的源码,看下FastThreadLocal是如何保存变量的。
public final void set(V value) {
//判断保存的值是不是UNSET这个对象,UNSET是InternalThreadLocalMap中
//定义的一个Object对象。
//因为InternalThreadLocalMap中用来保存变量的数组indexedVariables
//在创建或者扩容时,会把数组上每个空位置的值都设为UNSET
//所以这里自然不能让用户也往数组中存UNSET,不然就无法判断这个位置上的值
//是缺省值还是用户存入的变量副本了
if (value != InternalThreadLocalMap.UNSET) {
//获取当前Thread的InternalThreadLocalMap属性
InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get();
//往当前Thread的InternalThreadLocalMap属性的indexedVariables数组中
//对应的位置插入值,这个位置就是本FastThreadLocal对象的索引下标
//并且将当前的FastThreadLocal对象保存到待清理的Set中
setKnownNotUnset(threadLocalMap, value);
} else {
//如果用户set的是缺省值UNSET,使用remove()将线程保存的变量副本清除。
//即存入UNSET,Netty就认为你需要删除当前线程的FastThreadLocal变量副本
remove();
}
}
首先判断用户需要存入的是不是缺省值UNSET,如果不是的话开始往数组里插入值。
首先使用InternalThreadLocalMap.get()方法用来获取FastThreadLocalThread中的threadLocalMap属性。
public static InternalThreadLocalMap get() {
Thread thread = Thread.currentThread();
//如果线程是FastThreadLocalThread类型
if (thread instanceof FastThreadLocalThread) {
//获取其threadLocalMap属性
return fastGet((FastThreadLocalThread) thread);
} else {
//否则进入slowGet()方法
return slowGet();
}
}
里面做了个判断,如果线程对象是FastThreadLocalThread类型,则使用fastGet方法,获取FastThreadLocalThread中的threadLocalMap属性。
private static InternalThreadLocalMap fastGet(FastThreadLocalThread thread) {
//获取当前线程的InternalThreadLocalMap属性
InternalThreadLocalMap threadLocalMap = thread.threadLocalMap();
if (threadLocalMap == null) {
//如果是第一次获取,threadLocalMap为null,则创建一个
thread.setThreadLocalMap(threadLocalMap = new InternalThreadLocalMap());
}
return threadLocalMap;
}
如果当前线程不是FastThreadLocalThread类型,Netty为了代码的统一性,则在Thread类中threadLocals(用来封装ThreadLocal使用的哈希表)中保存一个InternalThreadLocalMap对象,并将其返回。这样,后面的存取代码不用因为当前线程的对象的类型不能再做区分。
//如果当前线程不是FastThreadLocalThread类型,
//则在Thread类中threadLocals(ThreadLocal使用)中
//保存一个InternalThreadLocalMap对象,并将其返回
private static InternalThreadLocalMap slowGet() {
//slowThreadLocalMap是一个ThreadLocal<InternalThreadLocalMap>类型的对象
//调用其get方法时,直接从Thread类中threadLocals(ThreadLocal使用)中获取
//线程本地保存的InternalThreadLocalMap实例
InternalThreadLocalMap ret = slowThreadLocalMap.get();
//如果线程的threadLocals中没有保存InternalThreadLocalMap实例
if (ret == null) {
//创建一个存入
ret = new InternalThreadLocalMap();
slowThreadLocalMap.set(ret);
}
return ret;
}
这里我们能发现一个问题,FastThreadLocal一定要结合FastThreadLocalThread使用,否则仍然会使用JDK原生的ThreadLocal来进行变量副本在线程本地的保存和读取。并且在线程本地的哈希表中(Thread类中的threadLocals属性中封装的table)还不是变量的值,而是使用InternalThreadLocalMap又将变量包裹了一层。因此,如果FastThreadLocal不结合FastThreadLocalThread使用的话,速度会变得比ThreadLocal更慢。
数据结构如下,网上盗的图,感觉画的不清楚,有时间笔者自己画一个。
再获取到FastThreadLocalThread中的threadLocalMap属性后,执行setKnownNotUnset(threadLocalMap, value)方法,将变量value存入threadLocalMap之中。
/*
* threadLocalMap:当前FastThreadLocalThread的InternalThreadLocalMap属性
* */
private void setKnownNotUnset(InternalThreadLocalMap threadLocalMap, V value) {
/*
* 往当前Thread的InternalThreadLocalMap属性的indexedVariables数组中,
* 在index位置上插入value值
* */
if (threadLocalMap.setIndexedVariable(index, value)) {
// 并将当前的FastThreadLocal对象保存到待清理的Set中
addToVariablesToRemove(threadLocalMap, this);
}
}
分为两步:
第一步:使用setIndexedVariable方法将变量存入threadLocalMap之中。
下面进入setIndexedVariable方法的源码。
public boolean setIndexedVariable(int index, Object value) {
//获取数组
Object[] lookup = indexedVariables;
//如果下标在数组长度范围之内
if (index < lookup.length) {
Object oldValue = lookup[index];
//值存入数组中
lookup[index] = value;
//返回数组中对应位置以前是否存过变量
return oldValue == UNSET;
} else {
//否则数组扩容
expandIndexedVariableTableAndSet(index, value);
return true;
}
}
第二步:使用addToVariablesToRemove方法并将当前的FastThreadLocal对象保存到待清理的Set中。
为了方便清理线程中存储的的FastThreadLocal变量副本,threadLocalMap中的数组indexedVariables的第1个位置(下标为0)不用了存储变量副本,而是存了一个Set集合。这个Set集合里面保存了全部的FastThreadLocal实例。等线程需要释放所有FastThreadLocal变量副本时,直接遍历这个Set集合再做处理即可。
/*
*
* 1)取下标index为0的数据(用于存储待清理的FastThreadLocal对象Set集合中),
* 如果该数据是缺省值UNSET或null,则会创建FastThreadLocal对象Set集合,并将
* 该Set集合填充到下标index为0的数组位置。
* 2)如果该数据不是缺省值UNSET,说明Set集合已金被填充,直接强转获取该Set集合。
* 3)最后将FastThreadLocal对象保存到待清理的Set集合中。
*
* threadLocalMap:当前线程的InternalThreadLocalMap属性
* variable:当前FastThreadLocal对象
* */
@SuppressWarnings("unchecked")
private static void addToVariablesToRemove(InternalThreadLocalMap threadLocalMap, FastThreadLocal<?> variable) {
// 取下标index为0的数据,用于存储待清理的FastThreadLocal对象Set集合中
// 用于存储待清理的FastThreadLocal对象Set集合
Object v = threadLocalMap.indexedVariable(variablesToRemoveIndex);
Set<FastThreadLocal<?>> variablesToRemove;
if (v == InternalThreadLocalMap.UNSET || v == null) {
//下标index为0的数据为空,则创建存储FastThreadLocal对象Set集合
variablesToRemove = Collections.newSetFromMap(new IdentityHashMap<FastThreadLocal<?>, Boolean>());
//将InternalThreadLocalMap中下标为0的数据,设置成FastThreadLocal对象Set集合
threadLocalMap.setIndexedVariable(variablesToRemoveIndex, variablesToRemove);
} else {
//转为Set类型
variablesToRemove = (Set<FastThreadLocal<?>>) v;
}
//将FastThreadLocal对象保存到待清理的Set中
variablesToRemove.add(variable);
}
FastThreadLocal的get()方法
get方法相对简单多,只需要从threadLocalMap中的数组里面取值即可。
public final V get() {
//获取当前线程的InternalThreadLocalMap属性
InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get();
//获取当前线程的InternalThreadLocalMap属性中indexedVariables数组中,
//对应索引位置的值
Object v = threadLocalMap.indexedVariable(index);
//如果索引位置上不是缺省值
if (v != InternalThreadLocalMap.UNSET) {
return (V) v;
}
//如果线程中没有存储当前FastThreadLocal的属性值,
//调用初始化方法
return initialize(threadLocalMap);
}
FastThreadLocal的清理
FastThreadLocal提供了一个可以用来清理当前线程存储的所有FastThreadLocal变量的方法,可以在线程执行任务结束时使用。防止线程对象没有垃圾回收,导致线程存储的所有FastThreadLocal变量都不能被垃圾回收。
/*
* 清理当前线程存储的所有FastThreadLocal变量
* */
public static void removeAll() {
//获取当前线程的InternalThreadLocalMap属性
InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.getIfSet();
if (threadLocalMap == null) {
return;
}
try {
//线程的InternalThreadLocalMap属性中的indexedVariables数组
//的第一个元素是用来存储已有FastThreadLocal变量的Set集合
Object v = threadLocalMap.indexedVariable(variablesToRemoveIndex);
//如果这个集合不是null
if (v != null && v != InternalThreadLocalMap.UNSET) {
@SuppressWarnings("unchecked")
Set<FastThreadLocal<?>> variablesToRemove = (Set<FastThreadLocal<?>>) v;
//将set集合转为数组
FastThreadLocal<?>[] variablesToRemoveArray =
variablesToRemove.toArray(new FastThreadLocal[0]);
//移除所有已存储的值
for (FastThreadLocal<?> tlv: variablesToRemoveArray) {
tlv.remove(threadLocalMap);
}
}
} finally {
//清除线程的threadLocalMap属性
InternalThreadLocalMap.remove();
}
}
里面做了两件事情,一是遍历数组的0位置上存储的Set集合,取出所有FastThreadLocal实例,然后依次调用其remove方法。
public final void remove(InternalThreadLocalMap threadLocalMap) {
if (threadLocalMap == null) {
return;
}
//数组的index位置重置为UNSET
Object v = threadLocalMap.removeIndexedVariable(index);
//Set集合中删除当前FastThreadLocal对象
removeFromVariablesToRemove(threadLocalMap, this);
//如果数组的index位置上原本有值
if (v != InternalThreadLocalMap.UNSET) {
try {
//onRemoval留给子类实现
onRemoval((V) v);
} catch (Exception e) {
PlatformDependent.throwException(e);
}
}
}
二是清除线程的threadLocalMap属性,InternalThreadLocalMap.remove()需要做的事情。
public static void remove() {
Thread thread = Thread.currentThread();
//如果线程是FastThreadLocalThread对象
if (thread instanceof FastThreadLocalThread) {
//将FastThreadLocalThread对象的threadLocalMap属性设为null,方便垃圾回收
((FastThreadLocalThread) thread).setThreadLocalMap(null);
} else {
//清理Thread类中的threadLocals中的哈希表
//调用的是ThreadLocal的remove()方法
slowThreadLocalMap.remove();
}
}
FastThreadLocalRunnable的作用
FastThreadLocalRunnable是一个实现Runnable接口的类,因此也是一个可被线程执行的任务。但是在其run()方法却执行了另一个Runnable对象的run()方法。表面上看上去怪怪的,其实这是对原Runnable对象的run()方法进行增强,让Runnable对象的run()方法执行完后,再增加执行一个FastThreadLocal.removeAll(),来清理线程存储的所有FastThreadLocal变量。
其源码比较简单:
final class FastThreadLocalRunnable implements Runnable {
//原Runnable对象
private final Runnable runnable;
private FastThreadLocalRunnable(Runnable runnable) {
this.runnable = ObjectUtil.checkNotNull(runnable, "runnable");
}
//run方法,对原Runnable对象的run方法进行增强
@Override
public void run() {
try {
//执行原Runnable对象的run方法
runnable.run();
} finally {
//增强内容:清除当前线程存储的所有FastThreadLocal变量
FastThreadLocal.removeAll();
}
}
//将Runnable对象封装成FastThreadLocalRunnable对象
static Runnable wrap(Runnable runnable) {
return runnable instanceof FastThreadLocalRunnable ? runnable : new FastThreadLocalRunnable(runnable);
}
}
一般在需要使用FastThreadLocal时,不仅需要使用创建FastThreadLocalThread对象的方式来创建线程,也需要使用FastThreadLocalRunnable的wrap(Runnable runnable)方法将原本线程需要执行的Runnable对象封装成FastThreadLocalRunnable对象,对原Runnable对象的run方法进行增强。让线程执行结束后,自动调用FastThreadLocal.removeAll()清理FastThreadLocal变量。
这里可以回顾一下我在《Netty源码解析之线程池的实现(二):创建线程与执行任务》文章写到的线程工厂DefaultThreadFactory里面创建线程的方法。
@Override
public Thread newThread(Runnable r) {
//1、将Runnable任务封装成FastThreadLocalRunnable
//2、拼接线程名称
//3、调用同名的newThread方法
Thread t = newThread(FastThreadLocalRunnable.wrap(r), prefix + nextId.incrementAndGet());
/*省略无关代码*/
return t;
}
//创建一个线程实例,FastThreadLocalThread
protected Thread newThread(Runnable r, String name) {
//创建线程,传入增强后的Runnable对象
return new FastThreadLocalThread(threadGroup, r, name);
}
可以看出,线程工厂DefaultThreadFactory里面创建线程时,就是将原Runnable对象封装成FastThreadLocalRunnable,并结合FastThreadLocalThread来使用FastThreadLocal!