在 第二章 ThreadLocal源码解析 详细分析了jdk本身的ThreadLocal源码。本节来看一下netty的FastThreadLocal的设计与源码解析。相关的源码已经抽取为一个框架:https://github.com/zhaojigang/concurrent-framework。
- 一、使用姿势
- 二、数据结构
- 三、源码分析
- 四、回收机制
- 五、FastThreadLocal优缺点
- 六、小工具:基于ConcurrentHashMap实现的ConcurrentSet
- 七、总结
一、使用姿势
private static final FastThreadLocal fastThreadLocal1 = new FastThreadLocal(){
@Override
protected Integer initialValue() throws Exception {
return 100;
}
@Override
protected void onRemoved(Integer value) throws Exception {
System.out.println(value + ":我被删除了");
}
};
private static final FastThreadLocal fastThreadLocal2 = new FastThreadLocal(){
@Override
protected String initialValue() throws Exception {
return "haha";
}
@Override
protected void onRemoved(String value) throws Exception {
System.out.println(value + ":我被删除了");
}
};
创建了两个FastThreadLocal实例,都重写了其initialValue()和onRemoved(Integer value)方法。
1.1、普通线程使用FastThreadLocal
@Test
public void testSetAndGetByCommonThread() {
Integer x = fastThreadLocal1.get();
String s = fastThreadLocal2.get();
fastThreadLocal1.set(200);
fastThreadLocal2.set("hehe");
}
1.2、FastThreadLocalThread使用FastThreadLocal
@Test
public void testSetAndGetByFastThreadLocalThread() {
new FastThreadLocalThread(()->{
Integer x = fastThreadLocal1.get();
String s = fastThreadLocal2.get();
fastThreadLocal1.set(200);
fastThreadLocal2.set("hehe");
}).start();
}
1.3、FastThreadLocalThread线程池使用FastThreadLocal
private static final Executor executor = FastThreadExecutors.newCachedFastThreadPool("test");
@Test
public void testSetAndGetByFastThreadLocalThreadExecutor() {
executor.execute(()->{
Integer x = fastThreadLocal1.get();
String s = fastThreadLocal2.get();
fastThreadLocal1.set(200);
fastThreadLocal2.set("hehe");
});
}
FastThreadExecutors是笔者自己写的一个线程全部是FastThreadLocalThread的线程池构造器。
1.4、FastThreadLocalThread线程池与promise结合使用
private static final AtomicInteger index = new AtomicInteger();
private static final Executor promiseTest = FastThreadExecutors.newCachedFastThreadPool("nettyPromiseTest");
@Test
public void testFastThreadPoolWithPromise() {
DefaultPromise promise = new DefaultPromise<>();
// 1、为promise添加10个监听器FutureListener
for (int i = 0; i < 10; i++) {
promise.addListener(future -> System.out.println("haha:" + index.getAndIncrement()));
}
// 2、使用线程池执行业务逻辑(这里只是设置promise的值,触发promise的监听器执行操作)
promiseTest.execute(() -> promise.setSuccess(null));
}
二、数据结构
对于jdk的ThreadLocal来讲,其底层数据结构就是一个Entry[]数组,key为ThreadLocal,value为对应的值(hash表);通过线性探测法解决hash冲突。对于FastThreadLocal来讲,底层数据结构就是单纯的简单数组Object[]。说明:
- 左图是FastThreadLocalThread(简称ftlt)使用FastThreadLocal(简称ftl)的结构图;
- 右图是普通线程使用FastThreadLocal和ThreadLocal(简称tl)的结构图。
2.1、ftlt使用ftl
- 每一个ftlt内部都有一个InternalThreadLocalMap实例,其底层数据结构就是一个Object[],初始length==32
- 数组的第一个元素index0存储一个
Set
的set集合,存储所有有效的ftl。每当有一个ftl的value设置到数组中的时候,就会将当前的ftl对象添加到Object[0]的set集合中;每当有一个ftl的value被从数组中删除的时候,就会将当前的ftl对象从Object[0]的set集合中删除> - Object[]的其余元素存储ftl的value,注意存储的不是key-value对象,只是value,而数组下标index是一个ftl的实例属性(二者唯一对应)
2.2、普通线程使用ftl
ftl支持普通线程对其进行使用。InternalThreadLocalMap中有一个tl变量
static final ThreadLocal slowThreadLocalMap = new ThreadLocal<>();
普通线程在设置或者初始化(包括get时进行的初始化)ftl时,会将slowThreadLocalMap作为key,通过其hashcode进行计算之后,存储到ThreadLocalMap的Entry[]的某一个节点上。值得注意的是,其value就是一个InternalThreadLocalMap对象。之后该线程上所有对于ftl的操作,都将先获取slowThreadLocalMap这个tl的value--InternalThreadLocalMap对象,然后和2.1一样对其进行操作。
三、源码分析
3.1、ftl的创建
======================FastThreadLocal========================
/**
* 每一个FastThreadLocal都有一个唯一标识
*/
private final int index;
/**
* 每一个FastThreadLocal类都会将自己添加到indexedVariables[variablesToRemoveIndex]处的Set>
*/
private static final int VARIABLES_TO_REMOVE_INDEX = InternalThreadLocalMap.nextVariableIndex();
/**
* 创建一个FastThreadLocal
*/
public FastThreadLocal() {
index = InternalThreadLocalMap.nextVariableIndex();
}
====================== InternalThreadLocalMap ========================
/**
* FastThreadLocal的唯一索引生成器
*/
private static final AtomicInteger nextIndex = new AtomicInteger();
/**
* 获取FastThreadLocal的唯一索引
*/
public static Integer nextVariableIndex() {
Integer index = nextIndex.getAndIncrement();
if (index < 0) {
nextIndex.decrementAndGet();
throw new IllegalStateException("too many thread-local variable index");
}
return index;
}
ftl的创建很简单。
- 初始化一个类变量:VARIABLES_TO_REMOVE_INDEX,通常是0。该索引位置用于存储所有有效的ftl对象的set集合;
- 初始化一个index实例变量,该变量就是ftl的唯一标识,类比tl的threadLocalHashCode(实际上二者不同的是,ftl的index可以直接作为数组下标进行操作,而tl需要threadLocalHashCode&(len-1)才可以);该index也是绑定的ftl对象的value在Object[]数组中的索引位置;
- index的生成使用对一个全局不可变变量的cas操作,保证唯一性;最多不能超过Integer.MAX_VALUE
3.2、ftl的获取
/**
* 获取当前线程的InternalThreadLocalMap中的当前ftl的value
*/
public V get() {
// 1、获取InternalThreadLocalMap
InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get();
// 2、从InternalThreadLocalMap获取索引为index的value,如果该索引处的value是有效值,不是占位值,则直接返回
Object value = threadLocalMap.indexedVariable(index);
if (value != InternalThreadLocalMap.UNSET) {
return (V) value;
}
// 3、indexedVariables[index]没有设置有效值,执行初始化操作,获取初始值
V initialValue = initialize(threadLocalMap);
// 4、注册资源清理器:当该ftl所在的线程不强可达(没有强引用指向该线程对象)时,清理其上当前ftl对象的value和set>中当前的ftl对象
registerCleaner(threadLocalMap);
return initialValue;
}
3.2.1 获取InternalThreadLocalMap
/**
* 获取InternalThreadLocalMap实例
*/
public static InternalThreadLocalMap get() {
Thread current = Thread.currentThread();
if (current instanceof FastThreadLocalThread) {
return fastGet((FastThreadLocalThread) current);
}
return slowGet();
}
如果当前线程是ftlt线程,则使用fastGet进行获取;否则使用slowGet进行获取。先来看fastGet:
private static InternalThreadLocalMap fastGet(FastThreadLocalThread current) {
InternalThreadLocalMap threadLocalMap = current.threadLocalMap();
if (threadLocalMap == null) {
threadLocalMap = new InternalThreadLocalMap();
current.setThreadLocalMap(threadLocalMap);
}
return threadLocalMap;
}
对于每一个ftlt,都有一个属性:
/**
* 类比Thread的ThreadLocal.ThreadLocalMap threadLocals属性
*/
private InternalThreadLocalMap threadLocalMap;
如果该threadLocalMap已经实例化过,则直接返回,否则,先创建一个InternalThreadLocalMap实例,然后将该实例设置到ftlt的threadLocalMap属性中。
/**
* InternalThreadLocalMap的底层数据结构
* 其index就是FastThreadLocal的唯一标记index,
* value是相对应的FastThreadLocal所要存储的值
*/
private Object[] indexedVariables;
/**
* 无效的value值(占位符),不使用null做无效值的原因是因为netty认为null也是一个有效值,
* 例如:假设没有重写FastThreadLocal的initialValue()方法,则该方法返回为null,netty会将null作为有效值直接存储起来
*/
public static final Object UNSET = new Object();
/**
* 创建indexedVariables数组,并将每一个元素初始化为UNSET
*/
public InternalThreadLocalMap() {
indexedVariables = new Object[32];
Arrays.fill(indexedVariables, UNSET);
}
再来看一下slowGet:
/**
* 兼容非FastThreadLocalThread
*/
private static final ThreadLocal slowThreadLocalMap = new ThreadLocal<>();
private static InternalThreadLocalMap slowGet() {
InternalThreadLocalMap threadLocalMap = slowThreadLocalMap.get();
if (threadLocalMap == null) {
threadLocalMap = new InternalThreadLocalMap();
slowThreadLocalMap.set(threadLocalMap);
}
return threadLocalMap;
}
这里就是典型的tl的操作了。之所以成为slowGet的原因是因为:
- fastGet可以直接从当前线程的属性获取;而slowGet需要根据slowThreadLocalMap的索引值与数组长度进行计算之后进行获取,如果没有直接根据索引命中的话,还可能需要进行线性探测的向后循环查找操作,当然还可能有一些清理和整理逻辑。
- fastGet设置InternalThreadLocalMap,直接给当前线程的属性赋值,而slowGet的set操作需要使用线性探测法进行设置,并会至少执行一次log级别的资源回收整理操作。
如上两点也是ftl比tl快的原因。但是可以看出tl在不断的回收无效的Entry使得新的Entry可以插入而不需要额外空间,但是ftl只能不断的增加index,不断向后增加,而index前边被remove掉的位置不能被重用,所以Object[]数组的size会越来越大。
3.2.2 从InternalThreadLocalMap获取值
/**
* 获取指定位置的元素
*/
public Object indexedVariable(int index) {
Object[] lookup = indexedVariables;
return index < lookup.length ? lookup[index] : UNSET;
}
3.2.3 初始化操作
private V initialize(InternalThreadLocalMap threadLocalMap) {
V v = null;
try {
//1、获取初始值
v = initialValue();
} catch (Exception e) {
throw new RuntimeException(e);
}
// 2、设置value到InternalThreadLocalMap中
threadLocalMap.setIndexedVariables(index, v);
// 3、添加当前的FastThreadLocal到InternalThreadLocalMap的Set>中
addToVariablesToRemove(threadLocalMap, this);
return v;
}
/**
* 初始化参数:由子类复写
*/
protected V initialValue() throws Exception {
return null;
}
首先调用由子类复写的initialValue(),如果没有复写,则直接返回null;之后进行值的设置操作;
/**
* 设置值
*/
public boolean setIndexedVariables(int index, Object value) {
Object[] lookup = indexedVariables;
if (index < lookup.length) {
Object oldValue = lookup[index];
lookup[index] = value;
return oldValue == UNSET;
} else {
expandIndexedVariables(index, value);
return true;
}
}
如果索引小于indexedVariables.length,直接获取indexedVariables[index];否则,进行扩容设置。
值得注意的是,setIndexedVariables方法仅仅当时新增的时候返回true,假设是修改的话,oldValue就不等于UNSET了,则返回false。
private void expandIndexedVariables(int index, Object value) {
Object[] oldArray = indexedVariables;
int oldCapacity = oldArray.length;
/**
* 计算新数组容量:获取>index的最小的2的n次方的数,例如:1->2 2->4 3->4 4->8 5->8 6->8 7->8 8->16
* Returns a power of two size for the given target capacity.
*
*
* {@link java.util.HashMap#tableSizeFor(int)}
* static final int tableSizeFor(int cap) {
* int n = cap - 1;
* n |= n >>> 1;
* n |= n >>> 2;
* n |= n >>> 4;
* n |= n >>> 8;
* n |= n >>> 16;
* return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
* }
*
*/
int newCapacity = index;
newCapacity |= newCapacity >>> 1;
newCapacity |= newCapacity >>> 2;
newCapacity |= newCapacity >>> 4;
newCapacity |= newCapacity >>> 8;
newCapacity |= newCapacity >>> 16;
newCapacity++;
/**
* 创建新数组并拷贝旧数组的元素到新数组
*/
Object[] newArray = Arrays.copyOf(oldArray, newCapacity);
/**
* 初始化扩容出来的部分的元素
*/
Arrays.fill(newArray, oldCapacity, newCapacity, UNSET);
/**
* 设置变量
*/
newArray[index] = value;
/**
* 将新数组设置给成员变量
*/
indexedVariables = newArray;
}
首先获取旧数组及其长度;然后进行新数组容量的计算(计算方式与1.8的HashMap一样:都是获取比给定值大的最小的2的n次方的数);然后创建新数组并拷贝旧数组元素到新数组,最后对扩容多出来的元素初始化为UNSET,然后设置value值,最后将新数组赋值给indexedVariables成员变量。
到此为止设置值的操作就结束了,最后:添加当前的FastThreadLocal到InternalThreadLocalMap的Set
/**
* 将当前的FastThreadLocal添加到indexedVariables[variablesToRemoveIndex]位置上的Set>中
*/
private void addToVariablesToRemove(InternalThreadLocalMap threadLocalMap, FastThreadLocal threadLocal) {
// 1、首先从InternalThreadLocalMap获取Set,如果存在,直接往Set里添加值FastThreadLocal;
// 如果不存在,则先创建一个Set,然后将创建的Set添加到InternalThreadLocalMap中,最后将FastThreadLocal添加到这个Set中
Object v = threadLocalMap.indexedVariable(VARIABLES_TO_REMOVE_INDEX);
// Set中的FastThreadLocal可能有多个类型,所以此处的泛型使用?,而不是用指定的V
Set> variablesToRemove;
if (v == InternalThreadLocalMap.UNSET) {
variablesToRemove = Collections.newSetFromMap(new IdentityHashMap, Boolean>());
threadLocalMap.setIndexedVariables(VARIABLES_TO_REMOVE_INDEX, variablesToRemove);
} else {
variablesToRemove = (Set>) v;
}
variablesToRemove.add(threadLocal);
}
步骤比较简单,先获取set集合,如果set集合不存在,则先创建set集合,之后将当前的ftl添加;否则直接进行添加。
3.2.4 注册资源清理器
/**
* 注册资源清理器:当该ftl所在的线程不强可达时,清理其上当前ftl的value和set>中当前的ftl
*/
private void registerCleaner(InternalThreadLocalMap threadLocalMap) {
Thread current = Thread.currentThread();
// 如果已经开启了自动清理功能 或者 已经对threadLocalMap中当前的FastThreadLocal开启了清理线程
if (FastThreadLocalThread.willCleanupFastThreadLocals(current) || threadLocalMap.isCleanerFlags(index)) {
return;
}
// 设置是否已经开启了对当前的FastThreadLocal清理线程的标志
threadLocalMap.setCleanerFlags(index);
// 将当前线程和清理任务注册到ObjectCleaner上去
ObjectCleaner.register(current, () -> remove(threadLocalMap));
}
这一块儿在回收机制中进行分析。
3.3、ftl的设置
/**
* 设置一个value
*/
public void set(V value) {
// 1、如果value是UNSET,表示删除当前的ThreadLocal对应的value;
// 如果不是UNSET,则可能是修改,也可能是新增;
// 如果是修改,修改value结束后返回,
// 如果是新增,则先新增value,然后新增ThreadLocal到Set中,最后注册Cleaner清除线程
if (value != InternalThreadLocalMap.UNSET) {
InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get();
if (setKnownNotUnset(threadLocalMap, value)) {
registerCleaner(threadLocalMap);
}
} else {
// 如果设置的值是UNSET,表示清除该FastThreadLocal的value
remove();
}
}
步骤见注释。
/**
* 返回true:如果是新添加了一个value;
* 返回false:如果是修改了一个value。
*/
private boolean setKnownNotUnset(InternalThreadLocalMap threadLocalMap, V value) {
// 新增value
if (threadLocalMap.setIndexedVariables(index, value)) {
addToVariablesToRemove(threadLocalMap, this);
return true;
}
// 修改value
return false;
}
3.4、ftl的删除
ftl有两种删除操作:
- 单个删除当前的ftl对象的值;
- 删除当前线程上的InternalThreadLocalMap中的每一个value以及threadLocalMap本身。
首先来看单个删除:
======================FastThreadLocal======================
/**
* 清除当前的FastThreadLocal
*/
private void remove() {
remove(InternalThreadLocalMap.getIfSet());
}
private void remove(InternalThreadLocalMap threadLocalMap) {
if (threadLocalMap == null) {
return;
}
// 1、从 InternalThreadLocalMap 中删除当前的FastThreadLocal对应的value
Object v = threadLocalMap.removeIndexedVariable(index);
// 2、从 InternalThreadLocalMap 中的Set>中删除当前的FastThreadLocal对象
removeFromVariablesToRemove(threadLocalMap, this);
// 3、如果删除的是有效值,则进行onRemove方法的回调
if (v != InternalThreadLocalMap.UNSET) {
try {
// 4、回调子类复写的onRemoved方法,默认为空实现
onRemoved((V) v);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
private void removeFromVariablesToRemove(InternalThreadLocalMap threadLocalMap, FastThreadLocal threadLocal) {
Object v = threadLocalMap.indexedVariable(VARIABLES_TO_REMOVE_INDEX);
if (v == InternalThreadLocalMap.UNSET || v == null) {
return;
}
Set> variablesToRemove = (Set>) v;
variablesToRemove.remove(threadLocal);
}
/**
* 当前的threadLocal被删除后的回调:由子类复写
*/
protected void onRemoved(V value) throws Exception {
}
====================== InternalThreadLocalMap ======================
/**
* 获取InternalThreadLocalMap实例,如果为null,则直接返回,不会创建;如果不为null,也直接返回
*/
public static InternalThreadLocalMap getIfSet() {
Thread current = Thread.currentThread();
if (current instanceof FastThreadLocalThread) {
return ((FastThreadLocalThread) current).threadLocalMap();
} else {
return slowThreadLocalMap.get();
}
}
/**
* 删除指定位置的对象
*/
public Object removeIndexedVariable(int index) {
Object[] lookup = indexedVariables;
if (index < lookup.length) {
// 1、获取旧值
Object v = lookup[index];
// 2、设置为UNSET
lookup[index] = UNSET;
// 3、返回旧值
return v;
} else {
return UNSET;
}
}
删除当前线程上的InternalThreadLocalMap中的每一个value以及threadLocalMap本身:
======================FastThreadLocal======================
/**
* 删除当前线程上的InternalThreadLocalMap中的每一个value以及threadLocalMap本身
*/
public static void removeAll() {
// 1、获取当前线程的InternalThreadLocalMap,如果当前的InternalThreadLocalMap为null,则直接返回
InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.getIfSet();
if (threadLocalMap == null) {
return;
}
try {
// 2、从indexedVariables[VARIABLES_TO_REMOVE_INDEX]获取目前InternalThreadLocalMap存储的有效的FastThreadLocal的值,之后遍历Set,进行remove操作
// 注意:这也是为什么我们会将有效的FastThreadLocal存储在一个Set中的原因(另外,如果没有Set>这个集合的话,我们需要直接去遍历整个indexedVariables数组,可能其中有效的并不多,影响效率)
Object v = threadLocalMap.indexedVariable(VARIABLES_TO_REMOVE_INDEX);
if (v != null && v != InternalThreadLocalMap.UNSET) {
Set> threadLocals = (Set>) v;
/**
* 这里为什么需要将set先转换为数组?
* 因为set的for-remove模式会报并发修改异常,array不会
*/
FastThreadLocal[] threadLocalArray = threadLocals.toArray(new FastThreadLocal[0]);
for (FastThreadLocal> threadLocal : threadLocalArray) {
threadLocal.remove();
}
}
} finally {
// 3、删除当前线程的InternalThreadLocalMap
threadLocalMap.remove();
}
}
====================== InternalThreadLocalMap ======================
/**
* 删除当前线程的InternalThreadLocalMap
*/
public void remove() {
Thread current = Thread.currentThread();
if (current instanceof FastThreadLocalThread) {
((FastThreadLocalThread) current).setThreadLocalMap(null);
} else {
slowThreadLocalMap.remove();
}
}
代码很简单,但是这里我们需要注意一点的就是封装性:例如对于InternalThreadLocalMap的remove操作,就应该封装在InternalThreadLocalMap类中(充血);而很多时候我们编写的代码会直接将InternalThreadLocalMap.remove()这一段代码写到用到的地方,这样就打破了封装性。
到此为止,ftl的基本源码除了回收机制之外,其他的就分析完了。
3.5、ftlt对普通Runnable任务的包装处理
public class FastThreadLocalRunnable implements Runnable {
private Runnable runnable;
public FastThreadLocalRunnable(Runnable runnable) {
this.runnable = ObjectUtil.checkNotNull(runnable, "runnable");
}
public static Runnable wrap(Runnable runnable) {
return runnable instanceof FastThreadLocalRunnable ? runnable : new FastThreadLocalRunnable(runnable);
}
@Override
public void run() {
try {
// 运行任务
this.runnable.run();
} finally {
/**
* 线程池中的线程由于会被复用,所以线程池中的每一条线程在执行task结束后,要清理掉其InternalThreadLocalMap和其内的FastThreadLocal信息,
* 否则,当这条线程在下一次被复用的时候,其ThreadLocalMap信息还存储着上一次被使用时的信息;
* 另外,假设这条线程不再被使用,但是这个线程有可能不会被销毁(与线程池的类型和配置相关),那么其上的ThreadLocal将发生资源泄露。
*/
FastThreadLocal.removeAll();
}
}
}
使用该类将一个普通的Runnable对象进行wrap装饰,之后在调用FastThreadLocalRunnable.run()的时候,实际上会调用真实对象(即普通的Runnable对象)的run(),执行完成之后,会进行对当前线程的全量回收操作(删除当前线程上的InternalThreadLocalMap中的每一个value以及threadLocalMap本身),这样就可以有效的在线程池中复用当前线程而不必关心ftl的错乱和泄漏问题。该类只会在ftlt中使用。下面来看一下ftlt:
public class FastThreadLocalThread extends Thread {
/**
* 类比Thread的ThreadLocal.ThreadLocalMap threadLocals属性
*/
private InternalThreadLocalMap threadLocalMap;
/**
* ThreadLocal与线程池问题:
* 线程池中的线程由于会被复用,所以线程池中的每一条线程在执行task结束后,要清理掉其InternalThreadLocalMap和其内的FastThreadLocal信息,
* 否则,当这条线程在下一次被复用的时候,其ThreadLocalMap信息还存储着上一次被使用时的信息;
* 另外,假设这条线程不再被使用,但是这个线程有可能不会被销毁(与线程池的类型和配置相关),那么其InternalThreadLocalMap和其内的FastThreadLocal信息将发生了资源泄露。
*
* 所以,如果一个Runnable任务被FastThreadLocalRunnable包裹,那么其InternalThreadLocalMap和其内的FastThreadLocal信息会被自动清理,此时:cleanupFastThreadLocals==true
* 否则,cleanupFastThreadLocals==false,此时线程需要注册到ObjectCleaner上,当线程不强可达时,由清理线程清理 其InternalThreadLocalMap和其内的FastThreadLocal信息
* 值得注意的是,如果在netty中如果普通线程执行任务(不会被FastThreadLocalRunnable包裹),还是要注意"ThreadLocal与线程池问题",
* netty对于普通线程仅仅是当线程不强可达时才会进行清理操作。
*/
private final boolean cleanupFastThreadLocals;
public FastThreadLocalThread() {
cleanupFastThreadLocals = false;
}
public FastThreadLocalThread(Runnable runnable) {
super(FastThreadLocalRunnable.wrap(runnable));
cleanupFastThreadLocals = true;
}
public FastThreadLocalThread(Runnable runnable, ThreadGroup threadGroup, String threadName) {
super(threadGroup, FastThreadLocalRunnable.wrap(runnable), threadName);
cleanupFastThreadLocals = true;
}
public void setThreadLocalMap(InternalThreadLocalMap threadLocalMap) {
this.threadLocalMap = threadLocalMap;
}
public InternalThreadLocalMap threadLocalMap() {
return this.threadLocalMap;
}
/**
* 是否会自动清理当前线程的"InternalThreadLocalMap和其内的FastThreadLocal信息"
*/
public boolean willCleanupFastThreadLocals() {
return this.cleanupFastThreadLocals;
}
/**
* 只有FastThreadLocalThread会作自动清理操作,其他类型的线程不会,
* 因为如果是普通线程执行一个被FastThreadLocalRunnable进行wrap过的Runnable任务,清除操作是FastThreadLocal.removeAll(),所以只能清除当前线程的ThreadLocalMap中key==slowThreadLocalMap的那个Entry,其他普通的Entry无法进行清除。
*/
public static boolean willCleanupFastThreadLocals(Thread current) {
return current instanceof FastThreadLocalThread
&& ((FastThreadLocalThread) current).willCleanupFastThreadLocals();
}
}
从上述两个类我们可以看出,只要是使用了ftlt,并且其执行的Runnable任务使用FastThreadLocalRunnable进行wrap,就可以达到自动清理的目的。二者缺一不可!如果是一个普通的线程执行一个被FastThreadLocalRunnable进行wrap过的Runnable任务,由于清除操作是FastThreadLocal.removeAll()
,所以只能清除当前线程的ThreadLocalMap中key==slowThreadLocalMap的那个Entry,其他普通的Entry无法进行清除。
3.6、ftlt线程工厂
public class FastThreadLocalThreadFactory implements ThreadFactory {
/**
* 线程池的ID,所有 FastThreadLocalThreadFactory实例共有
*/
private static AtomicInteger poolId = new AtomicInteger();
/**
* 线程池中的线程ID,FastThreadLocalThreadFactory实例私有
*/
private AtomicInteger nextId = new AtomicInteger();
/**
* 线程名称前缀
*/
private String prefix;
/**
* 是否是后台线程
*/
private boolean deamon;
/**
* 线程的优先级
*/
private int priority;
/**
* 线程组
*/
private ThreadGroup threadGroup;
public FastThreadLocalThreadFactory(String poolName) {
this(poolName, Thread.NORM_PRIORITY, false, Thread.currentThread().getThreadGroup());
}
public FastThreadLocalThreadFactory(String poolName, int priority, boolean deamon, ThreadGroup threadGroup) {
if (poolName == null) {
throw new NullPointerException("poolName");
}
if (priority > Thread.MAX_PRIORITY || priority < Thread.MIN_PRIORITY) {
throw new IllegalArgumentException("priority");
}
this.prefix = poolName + "-" + poolId.getAndIncrement() + "-";
this.priority = priority;
this.deamon = deamon;
this.threadGroup = threadGroup;
}
@Override
public Thread newThread(Runnable r) {
// 线程名称 poolName-poolId-nextId
Thread thread = new FastThreadLocalThread(r, threadGroup, prefix + nextId.incrementAndGet());
if (thread.isDaemon() != deamon) {
thread.setDaemon(deamon);
}
if (thread.getPriority() != priority) {
thread.setPriority(priority);
}
return thread;
}
}
从ftlt工厂的newThread(Runnable r)方法中可以看出,每一个普通的r都会被ftlt进行包裹,所以该ftlt创造出来的ftlt都具有自动清理的功能。
3.7、ftlt线程池构造器
为了方便使用ftlt线程池,这里创建了一个工具类,类比java.util.concurrent.Executors。
/**
* 类比 {@link java.util.concurrent.Executors}
*
* 线程池与Promise的结合使用见 promise-framework io.hulk.promise.framework.DefaultPromiseTest
*
* @author zhaojigang
* @date 2018/8/2
*/
public class FastThreadExecutors {
/**
* 创建一个线程数固定(corePoolSize==maximumPoolSize)的线程池
* 核心线程会一直存在,不被回收
* 如果一个核心线程由于异常跪了,会新创建一个线程
* 无界队列LinkedBlockingQueue
*/
public static Executor newFixedFastThreadPool(int nThreads, String poolName) {
return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(), new FastThreadLocalThreadFactory(poolName));
}
/**
* corePoolSize==0
* maximumPoolSize==Integer.MAX_VALUE
* 队列:SynchronousQueue
* 创建一个线程池:当池中的线程都处于忙碌状态时,会立即新建一个线程来处理新来的任务
* 这种池将会在执行许多耗时短的异步任务的时候提高程序的性能
* 60秒钟内没有使用的线程将会被中止,并且从线程池中移除,因此几乎不必担心耗费资源
*/
public static Executor newCachedFastThreadPool(String poolName) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<>(),
new FastThreadLocalThreadFactory(poolName));
}
/**
* 自定义各种参数
*/
public static Executor newLimitedFastThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime,
TimeUnit unit, BlockingQueue workQueue, String poolName,
RejectedExecutionHandler handler) {
return new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
new FastThreadLocalThreadFactory(poolName), handler);
}
}
四、回收机制
在netty中对于ftl提供了三种回收机制:
- 自动:使用ftlt执行一个被FastThreadLocalRunnable wrap的Runnable任务,在任务执行完毕后会自动进行ftl的清理。
- 手动:ftl和InternalThreadLocalMap都提供了remove方法,在合适的时候用户可以(有的时候也是必须,例如普通线程的线程池使用ftl)手动进行调用,进行显示删除。
- 自动:为当前线程的每一个ftl注册一个Cleaner,当线程对象不强可达的时候,该Cleaner线程会将当前线程的当前ftl进行回收。(netty推荐如果可以用其他两种方式,就不要再用这种方式,因为需要另起线程,耗费资源,而且多线程就会造成一些资源竞争)
下面来介绍第三种:在进行set操作时,当确定是新增一个ftl的时候,会执行如下方法:
======================FastThreadLocal======================
/**
* 注册资源清理器:当该ftl所在的线程不强可达时,清理其上当前ftl的value和set>中当前的ftl
*/
private void registerCleaner(InternalThreadLocalMap threadLocalMap) {
Thread current = Thread.currentThread();
// 如果已经开启了自动清理功能 或者 已经对threadLocalMap中当前的FastThreadLocal的开启了清理线程
if (FastThreadLocalThread.willCleanupFastThreadLocals(current) || threadLocalMap.isCleanerFlags(index)) {
return;
}
// 设置是否已经开启了对当前的FastThreadLocal清理线程的标志
threadLocalMap.setCleanerFlags(index);
// 将当前线程和清理任务注册到ObjectCleaner上去
ObjectCleaner.register(current, () -> remove(threadLocalMap));
}
====================== InternalThreadLocalMap ======================
/**
* BitSet简要原理:
* BitSet默认底层数据结构是一个long[]数组,开始时长度为1,即只有long[0],而一个long有64bit。
* 当BitSet.set(1)的时候,表示将long[0]的第二位设置为true,即0000 0000 ... 0010(64bit),则long[0]==2
* 当BitSet.get(1)的时候,第二位为1,则表示true;如果是0,则表示false
* 当BitSet.set(64)的时候,表示设置第65位,此时long[0]已经不够用了,扩容处long[1]来,进行存储
*
* 存储类似 {index:boolean} 键值对,用于防止一个FastThreadLocal多次启动清理线程
* 将index位置的bit设为true,表示该InternalThreadLocalMap中对该FastThreadLocal已经启动了清理线程
*/
private BitSet cleanerFlags;
/**
* 设置当前索引位置index(FastThreadLocal)的bit为1
*/
public void setCleanerFlags(int index) {
if (cleanerFlags == null) {
cleanerFlags = new BitSet();
}
cleanerFlags.set(index);
}
/**
* 获取 当前index的bit值,1表示true,0表示false(默认值)
*/
public boolean isCleanerFlags(int index) {
return cleanerFlags != null && cleanerFlags.get(index);
}
首先获取当前线程;然后判断当前线程是否开启了自定清理功能(也就是三种清理方式的第一种),如果开启了,则直接返回,否则,判断是否已经对threadLocalMap中当前的FastThreadLocal的开启了清理线程,如果是,也直接返回,否则先设置是否已经开启了对当前的FastThreadLocal清理线程的标志(关于BitSet的简要原理见注释),然后将当前线程和清理任务注册到ObjectCleaner上去。
/**
* from netty4.1
*/
public class ObjectCleaner {
/**
* 不强可达的Object的队列
*/
private static final ReferenceQueue
直接贴出全貌,依然是使用WeakReference,类似于ThreadLocal的Entry。
五、FastThreadLocal优缺点
优点:
- ftl使用了单纯的数组操作来替代了tl的hash表操作,所以在高并发的情况下,ftl操作速度更快。
- set操作:ftl直接根据index进行数组set,而tl需要先根据tl的hashcode计算数组下标(而ftl是直接获取),然后再根据线性探测法进行set操作,其间如果发生hash冲突且有无效的Entry时,还要进行Entry的清理和整理操作。最后不管是否冲突,都要进行一次log级别的Entry回收操作,所以慢了。
- get操作:ftl直接根据index进行获取,而tl需要先根据tl的hashcode计算数组下标,然后再根据线性探测法进行get操作,如果不能根据直接索引获取到value的话并且在向后循环遍历的过程中发现了无效的Entry,则会进行无效Entry的清理和整理操作。
- remove操作:ftl直接根据index从数组中删除当前的ftl的value,然后从Set集合中删除当前的ftl,之后还可以进行删除回调操作(功能增强);而tl需要先根据tl的hashcode计算数组下标,然后再根据线性探测法进行remove操作,最后还需要进行无效Entry的整理和清理操作。
- tl由于使用线性探测法,需要在get、set以及remove时做一些资源清理和整理操作,所以代码看上去不如ftl清晰明了。
缺点:
- ftl相较于tl不好的地方就是内存占用大,不会重复利用已经被删除(用UNSET占位)的数组位置,只会一味增大,是典型的“空间换时间”的操作。
六、小工具:基于ConcurrentHashMap实现的ConcurrentSet
/**
* from netty4.1
* 基于ConcurrentHashMap实现的支持并发的Set(实际上就是使用map的set,原理类比:HashSet和HashMap),所以在mina中该类被称为ConcurrentHashSet
*/
public class ConcurrentSet extends AbstractSet implements Serializable {
private static final long serialVersionUID = -1244664838594578508L;
/**
* 基本数据结构
*/
private ConcurrentMap map;
/**
* 创建ConcurrentSet实例
* 并初始化内部的ConcurrentHashMap
*/
public ConcurrentSet() {
map = new ConcurrentHashMap<>();
}
@Override
public boolean contains(Object o) {
return map.containsKey(o);
}
@Override
public boolean add(E e) {
return map.putIfAbsent(e, Boolean.TRUE) == null;
}
@Override
public boolean remove(Object o) {
return map.remove(o);
}
@Override
public Iterator iterator() {
return map.keySet().iterator();
}
@Override
public int size() {
return map.size();
}
}
七、总结
7.1、FastThreadLocal的get流程
- 根据线程类型获取InternalThreadLocalMap,如果是ftlt类型,直接获取currentThread.InternalThreadLocalMap;如果是普通类型,获取currentThread.ThreadLocalMap中key为slowThreadLocalMap的Entry,其value就是InternalThreadLocalMap实例。如果获取不到,创建InternalThreadLocalMap实例(底层是一个Object[]),之后设置到currentThread.InternalThreadLocalMap或者currentThread.ThreadLocalMap中key为slowThreadLocalMap的Entry中。
- 根据ftl的index值从InternalThreadLocalMap对象中查找value,如果查到的是有效值(不是UNSET),则直接返回;否则,执行初始化操作。
- 初始化操作:首先调用子类复写的initialValue()方法获取初始值,然后设置到InternalThreadLocalMap中当前的ftl的index值所在的位置处,因为是初始化,所以一定是新增的,所以此处直接将当前的ftl存储到InternalThreadLocalMap的Object[0]处的
Set
set集合中;最后返回初始值。> - 注册资源清理器:如果已经开启了自动清理功能或者已经注册过cleaner了,则直接返回,否则创建ftl清除线程
() -> remove(threadLocalMap)
;然后将当前线程current和ftl清除线程封装到AutomaticCleanerReference中;然后将该AutomaticCleanerReference对象添加到LIVE_SET并发set集合中;之后使用cas启动终极清理线程CLEAN_TASK,不断轮询LIVE_SET,从不强可达的AutomaticCleanerReference的引用队列中获取AutomaticCleanerReference对象,调用其内的清除线程进行清理操作(三步:从InternalThreadLocalMap中删除当前的ftl对应的value;从InternalThreadLocalMap中的Set
中删除当前的ftl对象;回调子类复写的onRemoved(V value)方法)。>
7.2、FastThreadLocal的set流程
- 如果设置的值是UNSET,则进行删除操作;
- 如果设置的值不是UNSET,首先获取InternalThreadLocalMap;
- 然后设置value到InternalThreadLocalMap中当前ftl的index值处,可能是新增,也可能是修改;如果是新增,将当前的ftl存储到InternalThreadLocalMap的Object[0]处的
Set
set集合中;> - 注册资源清理器:同5.1
7.3、FastThreadLocal的remove流程
单个删除:
- 获取当前线程的InternalThreadLocalMap对象,如果为null,直接返回,否则,
- 从InternalThreadLocalMap对象中删除当前的ftl的value
- 从InternalThreadLocalMap对象的Object[0]处的
Set
set集合中删除当前的ftl对象> - 回调onRemoved(V v)方法
全部删除:
- 获取当前线程的InternalThreadLocalMap对象,如果为null,直接返回,否则,
- 从InternalThreadLocalMap对象的Object[0]处获取
Set
set集合> - 遍历set集合中的每一个ftl,执行上述的“单个删除”操作
- 最后将当前线程的InternalThreadLocalMap对象置null 或者 将当前线程的ThreadLocalMap中的key为slowThreadLocalMap的tl移除掉。