概述
FastThreadLocal
的类名本身就充满了对ThreadLocal
的挑衅,“快男”FastThreadLocal是怎么快的?源码中类注释坦白如下:
/**
* ...
* Internally, a {@link FastThreadLocal} uses a constant index in an array, instead of using hash code and hash table,
* to look for a variable. Although seemingly very subtle, it yields slight performance advantage over using a hash
* table, and it is useful when accessed frequently.
* ...
*/
大概意思就是:用索引
代替了ThreadLocal中的threadLocalHashCode
,当请求频繁时,这个小改动就会显现其效果。
提到FastThreadLocal,就不得不提它的好基友FastThreadLocalThread
,简单来说,FastThreadLocal就是为FastThreadLocalThread量身打造的!
FastThreadLocalThread又是哪个单位的?且听我慢慢道来……
FastThreadLocalThread
一般来说,Netty的client端,是这么创建的:
EventLoopGroup group = new NioEventLoopGroup();
沿着调用链,层层深入NioEventLoopGroup的构造函数,在MultithreadEventExecutorGroup
的构造块,会看到这样的逻辑:
if (executor == null) {
executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
}
newDefaultThreadFactory()
方法,功能如其名,创建了DefaultThreadFactory
工厂:
protected ThreadFactory newDefaultThreadFactory() {
return new DefaultThreadFactory(getClass());
}
而DefaultThreadFactory工厂的产品,就是FastThreadLocalThread。
ok,FastThreadLocalThread物种起源的事儿暂且放下,我们来简单过一下原版ThreadLocal的工作机制。
ThreadLocal
ThreadLocal<T>
大家应该并不陌生,两个最核心API
//赋值方法
public void set(T value);
//取值方法
public T get();
用以并发环境下,线程生命周期内的存取数据操作,隔离其它线程干扰。
实现原理浅谈
帮助理解的示意图:
Thread中有一属性threadLocals
,ThreadLocal.ThreadLocalMap
类型(ThreadLocalMap是ThreadLocal的静态内部类)。ThreadLocal的set(T value)
方法被调用时,会将参数value
存放于当前线程的threadLocals
中,想要获取时,再从threadLocals
中获取。
刚刚说过,threadLocals是一个ThreadLocalMap(ThreadLocal中的静态内部类),Entry则是ThreadLocalMap的内部节点
。而ThreadLocalMap作为一个Map,人设是这样的ThreadLocalMap<ThreadLocal<T>>,T>
,即key=ThreadLocal<T>,value=T。
- 存入 set(T value)
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t); //根据当前线程获取ThreadLocalMap
if (map != null)
map.set(this, value); //map的key直接使用的当前ThreadLocal对象
else
createMap(t, value);
}
线程向ThreadLocal存入时,第一次调用将在线程中分配一块空间,初始大小为Entry[16]数组
。然后,以作为key的ThreadLocal<T>计算出hashCode
,稍加计算得出Entry[16]数组
的索引i
。最后,为槽位Entry[i]赋值上value。
- 获取 T get()
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t); //找到与当前线程绑定的ThreadLocalMap
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
获取时,先找到与当前线程绑定的ThreadLocalMap,然后你懂得……
听上去一副满满的HashMap的套路,其实内在有很大差别,比如ThreadLocal的Entry实现了WeakReference
(弱引用)。
一个小问题
ThreadLocal为什么用Entry数组作为内置实现?而且初始就16位,还有扩容等实现。对某一线程而言,ThreadLocal不是只能存储一个值吗?这么想的话,用单个Object就能存储了……
这个万恶的问题,当时困扰了我很长时间,其实想通后很简单:
Entry数组为了实现ThreadLocalMap,而ThreadLocalMap君的key是ThreadLocal,多个ThreadLocal都可以存储在ThreadLocalMap中。没错,A线程只用一个ThreadLocal的话,确实用一个Object作为内置实现就搞定了,但是A线程使用多个ThreadLocal时,无法满足!
FastThreadLocal
ThreadLocal就分析到这里,让我们回归FastThreadLocal。
结构及关键方法示意图:
前文提到过,FastThreadLocal的关键是index
,index
可以理解成一个FastThreadLocal对象的唯一ID。这个index会在FastThreadLocalThread线程中,作为其indexedVariables
属性(初始是一个Object[32]数组)的索引,达成与ThreadLocal类似的效果。
值得一提的是Object[] indexedVariables
的第0位(Object[0]),用于存放未来会被释放的数据,其实存储的就是之前FastThreadLocal的set方法操作过的槽位信息。FastThreadLocal的remove方法被调用时,会根据记录的槽位信息进行大扫除。
FastThreadLocal相关内容就聊到这里,更多细节请翻看源码!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。