说到Netty的FastThreadLocal类,自然而然的想到JDK的ThreadLocal,为什么Netty觉得ThreadLocal不好?不够快吗?那么这里直接说下ThreadLocal存在的问题。
1、JDK ThreadLocal的问题
Thread中的ThreadLocalMap存储ThreadLocal,ThreadLocalMap内部使用ThreadLocalMap.Entry数组存储每一个ThreadLocal,存储计算和HashMap类似,要计算key的索引位置=key.threadLocalHashCode&(len-1),中间可能需要计算冲突,使用的是线程探测方法(当前索引在被占用下,使用下一个索引)。达到一定条件后,还需扩充数组长度,rehash,效率不是太高。另外,还需要使用者注意内存泄漏问题。
2、Netty FastThreadLocal 源码解读
这里主要从get,set 方法角度看代码,具体的实现细节可以跟代码查看。
public FastThreadLocal() { // 初始化当前线程对象下标索引index
index = InternalThreadLocalMap.nextVariableIndex(); // 初始化当前线程对象下标清除标记索引index
cleanerFlagIndex = InternalThreadLocalMap.nextVariableIndex();
}
// 获得对象
public final V get() { // 获得当前线程对应的InternalThreadLocalMap,通过索引index从数组找到对应对象
InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get();
Object v = threadLocalMap.indexedVariable(index); if (v != InternalThreadLocalMap.UNSET) { //
return (V) v;
} // InternalThreadLocalMap获取不到对象,则通过初始化方法获取默认值
V value = initialize(threadLocalMap); // threadLocalMap注册到清理器,这里暂时不深究,猜测与垃圾回收或防止内存泄露有关
registerCleaner(threadLocalMap);
return value;
}
// 初始化
private V initialize(InternalThreadLocalMap threadLocalMap) {
V v = null; try {
v = initialValue();
} catch (Exception e) {
PlatformDependent.throwException(e);
} // 将当前对象索引index与对象进行绑定
threadLocalMap.setIndexedVariable(index, v);
addToVariablesToRemove(threadLocalMap, this); return v;
}
// 初始化值默认为null,子类可实现默认值
protected V initialValue() throws Exception { return null;
}
// 设置对象
public final void set(V value) { if (value != InternalThreadLocalMap.UNSET) {
InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get(); if (setKnownNotUnset(threadLocalMap, value)) {
registerCleaner(threadLocalMap);
}
} else {
remove();
}
}
// 将对象设置进非默认对象对应的索引下标处
private boolean setKnownNotUnset(InternalThreadLocalMap threadLocalMap, V value) { if (threadLocalMap.setIndexedVariable(index, value)) {
addToVariablesToRemove(threadLocalMap, this); return true;
} return false;
}
// InternalThreadLocalMap#setIndexedVariable,在对应的索引index数组上设置对象
public boolean setIndexedVariable(int index, Object value) {
Object[] lookup = indexedVariables; if (index < lookup.length) { // 查询OldValue,替换新对象,返回旧值是否为默认未创建的对象,也就是说对应的值是不是新对象
Object oldValue = lookup[index];
lookup[index] = value; return oldValue == UNSET;
} else { // 扩容索引表,并且进行设值
expandIndexedVariableTableAndSet(index, value); return true;
}
}
总结:
1)FastThreadLocal初始化的时候,就会默认生成确定的索引下标,InternalThreadLocalMap存放索引表(数组),这样便于快速的找到对象。
2)针对remove方法,同样在类初始化的时候variablesToRemoveIndex会生成确定的索引下标,每次在进行set时都会将对应的FastThreadLocal存放到threadLocalMap中,这样方便在removeAll时进行清理对应的对象,防止内存泄露。
3)每次创建对象时,跟JDK ThreadLocal比都会相应的增加了很多索引下标,这在创建海量的FastThreadLocal对象时,数组占用的空间也不可小觑,所以这是Netty高性能之处:空间换时间的做法。
参考:
https://www.jianshu.com/p/17e6989d647a
https://www.jianshu.com/p/3fc2fbac4bb7
扫描微信二维码↓↓↓,关注《**搬运工来架构**》
image
网友评论