1

概述

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();

用以并发环境下,线程生命周期内的存取数据操作,隔离其它线程干扰。

实现原理浅谈

帮助理解的示意图:
clipboard.png

Thread中有一属性threadLocalsThreadLocal.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。

结构及关键方法示意图:

clipboard.png

前文提到过,FastThreadLocal的关键是indexindex可以理解成一个FastThreadLocal对象的唯一ID。这个index会在FastThreadLocalThread线程中,作为其indexedVariables属性(初始是一个Object[32]数组)的索引,达成与ThreadLocal类似的效果。

值得一提的是Object[] indexedVariables的第0位(Object[0]),用于存放未来会被释放的数据,其实存储的就是之前FastThreadLocal的set方法操作过的槽位信息。FastThreadLocal的remove方法被调用时,会根据记录的槽位信息进行大扫除。

FastThreadLocal相关内容就聊到这里,更多细节请翻看源码!

感谢

ThreadLocal源码解读


青鱼
268 声望25 粉丝

山就在那里,每走一步就近一些