「我正在参与掘金会员专属活动-源码共读第一期,点击参与」
1.FastThreadLocal的大纲图及思路介绍
-
基于Netty 4.1.X版本,上述图描述的东西如下:
-
ThreadLocal 与 Netty FastThreadLocal的对比。(FastThreadLocal---空间换时间)
- FastThreadLocal优化的方案:取号入座方案
- 每往InternalThreadLocalMap中塞入一个新的FastThreadLocal对象,就给该FastThreadLocal对象发个唯一的下标,然后让该FastThreadLocal对象记住该下标,去InternalThreadLocalMap中找value时,直接通过该下标去取对应的value。
- 下标为0的地方保存所有FastThreadLocal的Set。
- 内存泄漏问题:规范使用(用完后remove掉即可)
- FastThreadLocal优化的方案:取号入座方案
-
InternalThreadLocalMap的核心逻辑图。
- 整体思路(空间换时间)
- 第一点:InternalThreadLocalMap采用Object数组来存储FastThreadLocal对象和其value。
- 第二点:第一个位置存放了一个包含所使用的FastThreadLocal对象的set。(目的便于后面的删除工作)
- 第三点:后面的位置存储所有的value。
- FastThreadLocal构造时已被分配了一个唯一下标,下标对应的是value所处的下标。
- 整体思路(空间换时间)
-
FastThreadLocal、InternalThreadLocalMap、FastThreadLocalThread的关系。
-
-
FastThreadLocal好处:
- 提高效率的方法:
- FastThreadLocal 通过分配下标直接定位 value ,不会有 hash 冲突。
- FastThreadLocal 采用空间换时间。
- 配套FastThreadLocalThread使用快速。(否则不如ThreadLocal)
- 防止内存泄漏:FastThreadLocal 最好配套 FastThreadLocalRunnable使用,执行完任务后会主动调用 removeAll 来移除所有 FastThreadLocal。
- 提高效率的方法:
-
FastThreadLocal缺点:可能浪费空间。
-
举例:一个系统中new了100个FastThreadLocal,那第100个FastThreadLocal的下标是100。
- 如果该线程之前都没塞过FastThreadLocal,此时塞入第一个FastThreadLocal,构造出来的数组长度是32,但是该FastThreadLocal的下标已涨到了100,所以这个线程第一次塞值,数组就需扩容。
- 白白浪费了前面的99个数组空间。
- Netty针对该数组的扩容是基于index的(基于index的向上2次幂取整),直接进行数组的拷贝,无需进行rehash。
- ThreadLocalMap扩容需进行rehash --- 重新基于key的hash值进行位置的设置。
- 如果该线程之前都没塞过FastThreadLocal,此时塞入第一个FastThreadLocal,构造出来的数组长度是32,但是该FastThreadLocal的下标已涨到了100,所以这个线程第一次塞值,数组就需扩容。
-
2.FastThreadLocal
2.1 重要属性
// 共同的不可变int值( variablesToRemoveIndex 的值为 0,它属于常量赋值,第一次调用时 nextIndex 的值为 0 。)
// 静态常量是跟着类的,一个类只会有一个
private static final int variablesToRemoveIndex = InternalThreadLocalMap.nextVariableIndex();
// 当前FastThreadLocal的唯一下标:在 FastThreadLocal 构造时就被赋值
// 常量变量是跟着对象的,每个对象的值都不一样
private final int index;
public FastThreadLocal() {
// 初始化唯一下标
index = InternalThreadLocalMap.nextVariableIndex();
}
// 为了填充 Cache Line,避免伪共享问题的产生
/** @deprecated These padding fields will be removed in the future. */
public long rp1, rp2, rp3, rp4, rp5, rp6, rp7, rp8, rp9;
- variablesToRemoveIndex:静态变量,每个 FastThreadLocal 共有的值。
- index:给每个 FastThreadLocal 发的唯一下标。
2.2 set方法
public final void set(V value) {
// 如果要塞入的值不是默认值
if (value != InternalThreadLocalMap.UNSET) {
// 获取当前线程中的InternalThreadLocalMap
InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get();
setKnownNotUnset(threadLocalMap, value);
} else {
// 否则移除
remove();
}
}
private void setKnownNotUnset(InternalThreadLocalMap threadLocalMap, V value) {
if (threadLocalMap.setIndexedVariable(index, value)) {
// 添加到待删除列表(this是FastThreadLocal对象)
addToVariablesToRemove(threadLocalMap, this);
}
}
-
步骤:
- 如果要塞入的值不是默认值,则保存。
- 获取当前线程中的InternalThreadLocalMap。
- 保存当前值到数组的指定下标。
- 保存当前FastThreadLocal对象到数组的下标为0的Set中。
- 如果塞入的是默认值,则进行移除动作。
- 如果要塞入的值不是默认值,则保存。
2.3 get方法
public final V get() {
// 获取线程中的InternalThreadLocalMap
InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get();
// 获取当前FastThreadLocal对应的唯一下标的值
Object v = threadLocalMap.indexedVariable(index);
// 值不是初始值,就直接返回
if (v != InternalThreadLocalMap.UNSET) {
return (V) v;
}
//如果是初始值,就执行初始化赋值操作
return initialize(threadLocalMap);
}
-
步骤:
- 获取线程中的InternalThreadLocalMap。
- 获取数组中 当前FastThreadLocal对应的唯一下标(index)的值。
- 如果是不是初始值,就直接返回。
- 如果是初始值,就执行初始化赋值操作。
2.4 removeAll方法
public static void removeAll() {
// 获取到当前线程的 InternalThreadLocalMap
InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.getIfSet();
if (threadLocalMap == null) {
return;
}
try {
// 得到下标为0的V(获取保存所有FastThreadLocal的Set)
Object v = threadLocalMap.indexedVariable(variablesToRemoveIndex);
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) {
// 调用remove方法
tlv.remove(threadLocalMap);
}
}
} finally {
// 将线程里面的map置空,完成整体移除
InternalThreadLocalMap.remove();
}
}
-
步骤:
- 获取到当前线程的 InternalThreadLocalMap。
- 获取保存所有FastThreadLocal的Set。
- 如果保存所有FastThreadLocal的Set 不为空的话,则循环从数组中移除每个FastThreadLocal对应值和本身。
- 最后将线程里面的map置空。
2.5 remove方法
public final void remove(InternalThreadLocalMap threadLocalMap) {
if (threadLocalMap == null) {
return;
}
// 移除当前FastThreadLocal的唯一下标的数据,并返回值
Object v = threadLocalMap.removeIndexedVariable(index);
// 将当前FastThreadLocal从数组的下标为0的Set中移除
removeFromVariablesToRemove(threadLocalMap, this);
// 如果移除的下标的值不是默认值的话
if (v != InternalThreadLocalMap.UNSET) {
try {
// 这是扩展,自行实现(可释放对象的内存等)
onRemoval((V) v);
} catch (Exception e) {
PlatformDependent.throwException(e);
}
}
}
// 将当前FastThreadLocal从数组的下标为0的Set中移除
private static void removeFromVariablesToRemove(
InternalThreadLocalMap threadLocalMap, FastThreadLocal<?> variable) {
// 获取下标为0的Set(保存所有FastTreadLocal的Set)
Object v = threadLocalMap.indexedVariable(variablesToRemoveIndex);
// set为空或者为默认值的话,则跳过
if (v == InternalThreadLocalMap.UNSET || v == null) {
return;
}
@SuppressWarnings("unchecked")
Set<FastThreadLocal<?>> variablesToRemove = (Set<FastThreadLocal<?>>) v;
// 调用set的remove
variablesToRemove.remove(variable);
}
-
步骤:
- 移除当前FastThreadLocal的唯一下标的数据,并返回值。
- 将当前FastThreadLocal从数组的下标为0的Set中移除。
- 如果移除的下标的值不是默认值的话,可以自定义实现。
2.6 addToVariablesToRemove 方法:内部私有方法
private static void addToVariablesToRemove(InternalThreadLocalMap threadLocalMap, FastThreadLocal<?> variable) {
// variablesToRemoveIndex值为0(从数组的0下标处获取所有FastTreadLocal的Set)
Object v = threadLocalMap.indexedVariable(variablesToRemoveIndex);
Set<FastThreadLocal<?>> variablesToRemove;
// 如果v 是默认值 或 v==null
if (v == InternalThreadLocalMap.UNSET || v == null) {
variablesToRemove = Collections.newSetFromMap(new IdentityHashMap<FastThreadLocal<?>, Boolean>());
// 将该Set塞到Object数组的第0个位置
threadLocalMap.setIndexedVariable(variablesToRemoveIndex, variablesToRemove);
} else {
// 将v强转为Set
variablesToRemove = (Set<FastThreadLocal<?>>) v;
}
// 将传入的FastThreadLocal塞到该set中
variablesToRemove.add(variable);
}
-
步骤:
- 从数组的0下标处获取所有FastTreadLocal的Set。
- 如果获取到的v为空/默认值的话,则初始化。
- 否则将v强转为Set<FastThreadLocal<?>>。
- 将传入的FastThreadLocal塞到这个set中。
3.InternalThreadLocalMap
3.1 重要属性
// 保存普通非FastThreadLocalThread使用FastThreadLocal时的 InternalThreadLocalMap
private static final ThreadLocal<InternalThreadLocalMap> slowThreadLocalMap =
new ThreadLocal<InternalThreadLocalMap>();
// 填充的默认值
public static final Object UNSET = new Object();
// 通过Object数组来实现
// 数组的第一个位置(index=0)放一个 保存所有使用的 FastThreadLocal 对象的 set
// 目的: 用于删除
private Object[] indexedVariables;
private InternalThreadLocalMap() {
indexedVariables = newIndexedVariableTable();
}
// 构造默认生成32长度的数组,并填充默认值
private static Object[] newIndexedVariableTable() {
Object[] array = new Object[INDEXED_VARIABLE_TABLE_INITIAL_SIZE];
Arrays.fill(array, UNSET);
return array;
}
-
nextIndex:发号器(通过AtomicInteger实现),为FastThreadLocal的 variablesToRemoveIndex、index属性生成值。
-
indexedVariables:通过Object数组来实现。
-
数组的第一个位置(index=0):保存所有使用的 FastThreadLocal 对象的 set 。
- 目的: 便于删除数据。
-
数组其它位置保存真实值。
-
-
slowThreadLocalMap:保存普通非FastThreadLocalThread使用FastThreadLocal时的 InternalThreadLocalMap。
3.2 setIndexedVariable方法
public boolean setIndexedVariable(int index, Object value) {
// 保存数据的Object数组
Object[] lookup = indexedVariables;
// 重点:默认数组是32,如果index大于数组长度,则需扩容。
// 如果第一个塞入的值的线程的index为100,则仅只有该一个值,数组就需扩容
if (index < lookup.length) {
// 直接通过索引找到位置进行替换
Object oldValue = lookup[index];
lookup[index] = value;
return oldValue == UNSET;
} else {
// 进行扩容
expandIndexedVariableTableAndSet(index, value);
return true;
}
}
-
步骤:
- 判断 index 是否大于数组长度。
- 如果index大于数组长度,则需扩容。
- 否则直接通过index找到位置进行替换即可。
-
缺点:如果第一个塞入的值的线程的index为100,则仅仅只有这么一个值,数组就需扩容。
3.3 removeIndexedVariable方法
// 把对应下标的值设置为默认值,然后返回原值
public Object removeIndexedVariable(int index) {
Object[] lookup = indexedVariables;
if (index < lookup.length) {
Object v = lookup[index];
// 找到位置,覆盖成初始化值
lookup[index] = UNSET;
return v;
} else {
return UNSET;
}
}
-
步骤:
- 如果下标小于数组长度,则找到位置,使用默认值覆盖,然后返回旧值。
- 如果下标不小于数组长度,则直接返回默认值。
3.4 expandIndexedVariableTableAndSet方法:扩容方法
// 数据扩容是基于index而不是原先数组的大小
private void expandIndexedVariableTableAndSet(int index, Object value) {
Object[] oldArray = indexedVariables;
final int oldCapacity = oldArray.length;
// 基于index的向上2次幂取整(2次幂取整跟HashMap类似)
int newCapacity = index;
newCapacity |= newCapacity >>> 1;
newCapacity |= newCapacity >>> 2;
newCapacity |= newCapacity >>> 4;
newCapacity |= newCapacity >>> 8;
newCapacity |= newCapacity >>> 16;
newCapacity ++;
// 直接数组拷贝,无需rehash(优于ThreadLocalMap的地方,ThreadLocalMap需rehash,重新基于Key的hash值进行位置的分配)
Object[] newArray = Arrays.copyOf(oldArray, newCapacity);
Arrays.fill(newArray, oldCapacity, newArray.length, UNSET);
// 设置目标下标的值
newArray[index] = value;
indexedVariables = newArray;
}
-
步骤:
- 基于index的向上2次幂取整(2次幂取整跟HashMap类似)
- 直接数组拷贝。(无需rehash,因为FastThreadLocal的index都是固定的)
- 设置目标下标的值。
3.5 indexedVariable方法:获取某个下标(某个FastThreadLocal)的数据
// 直接进行一个数组的下标获取
public Object indexedVariable(int index) {
Object[] lookup = indexedVariables;
return index < lookup.length? lookup[index] : UNSET;
}
- 步骤:直接进行一个数组的下标获取。
3.6 get方法
// 获取当前线程的InternalThreadLocalMap
public static InternalThreadLocalMap get() {
// 获取当前线程
Thread thread = Thread.currentThread();
// FastThreadLocalThread相关的类的话,则返回FastThreadLocalThread下的threadLocalMap字段
if (thread instanceof FastThreadLocalThread) {
// 如果当前线程是fastThread类型,就执行fastGet
return fastGet((FastThreadLocalThread) thread);
} else {
// 否则就slowGet
return slowGet();
}
}
private static InternalThreadLocalMap fastGet(FastThreadLocalThread thread) {
// 获取InternalThreadLocalMap
InternalThreadLocalMap threadLocalMap = thread.threadLocalMap();
//为空就new一个
if (threadLocalMap == null) {
thread.setThreadLocalMap(threadLocalMap = new InternalThreadLocalMap());
}
// 直接返回
return threadLocalMap;
}
// 从当前线程 中的 threadLocals字段下,通过ThreadLocal获取对应的 InternalThreadLocalMap
private static InternalThreadLocalMap slowGet() {
InternalThreadLocalMap ret = slowThreadLocalMap.get();
if (ret == null) {
// new一个新的InternalThreadLocalMap放入到当前线程的threadLocals变量中
ret = new InternalThreadLocalMap();
slowThreadLocalMap.set(ret);
}
return ret;
}
-
作用:获取当前线程的InternalThreadLocalMap
-
步骤:
- 获取当前线程。
- 如果是FastThreadLocalThread相关的类的话,则返回FastThreadLocalThread下的threadLocalMap字段。
- 如果不是FastThreadLocalThread相关的类的话,从当前线程 中的 threadLocals字段下,通过ThreadLocal获取对应的 InternalThreadLocalMap。
4.FastThreadLocalThread
4.1 重要属性
// 当前线程保存的变量(通过FastThreadLocal查询)
private InternalThreadLocalMap threadLocalMap;
- threadLocalMap: 当前线程保存的变量(通过FastThreadLocal查询)
4.2 构造方法
public FastThreadLocalThread(Runnable target) {
// 包装成FastThreadLocalRunnable
super(FastThreadLocalRunnable.wrap(target));
cleanupFastThreadLocals = true;
}
- 将Runnbale包装为FastThreadLocalRunnable。
5.FastThreadLocalRunnable
5.1 重要属性
// 被包装的Runnable
private final Runnable runnable;
- runnable:被包装的真实的任务。
5.2 run方法
@Override
public void run() {
try {
runnable.run();
} finally {
// Runnable 执行完毕之后,会主动调用 FastThreadLocal.removeAll() 来清理所有的 FastThreadLocal
FastThreadLocal.removeAll();
}
}
- Runnable 执行完毕后,主动清理所有的 FastThreadLocal。
5.3 包装情况
static Runnable wrap(Runnable runnable) {
return runnable instanceof FastThreadLocalRunnable ? runnable : new FastThreadLocalRunnable(runnable);
}
- 将Runnable类包装成FastThreadLocalRunnable。
6.ThreadLocal与FastThreadLocal的性能对比情况
-
普通Thread+ThreadLocal/FastThreadLocal对比:
-
代码:io.netty.microbench.concurrent.FastThreadLocalSlowPathBenchmark
-
结果:
-
- 建议:使用普通Thread的情况下,建议使用ThreadLocal即可。
-
FastThreadLocalThread+ThreadLocal/FastThreadLocal对比:
- 代码:io.netty.microbench.concurrent.FastThreadLocalFastPathBenchmark
- 结果:
- 建议:使用FastThreadLocalThread的情况下,建议使用FastThreadLocal即可。
7.Dubbo对好代码的"Ctrl+C/V "
- dubbo的org.apache.dubbo.common.threadlocal包下的InternalThreadLocal、InternalThreadLocalMap、InternalThread类 是分别参照 Netty的FastThreadLocal、InternalThreadLocalMap、FastThreadLocalThread类进行编写的。(此处吐槽下,好的代码我们直接Ctrl+C、Ctrl+V 哈哈哈哈)