Netty源码学习(七)FastThreadLocal

0. FastThreadLocal简介

如同注释中所说:A special variant of ThreadLocal that yields higher access performance when accessed from a FastThreadLocalThread.

这是ThreadLocal的变种,但是有更高的性能

ps.本文涉及的源码版本如下:

JDK : java-1.8.0-openjdk-1.8.0.141-1.b16.ojdkbuild.windows.x86_64

Netty : 4.1.15.Final

 

1. JDK自带的ThreadLocal的工作原理,以及存在的问题

原理简介:

a. 每个线程内部维护了一个ThreadLocal.ThreadLocalMap类型的变量threadLocals

b. ThreadLocalMap是由数组实现的Map,key为ThreadLocal,value为对应的变量

c. 对ThreadLocal进行get/set操作时,会先获取当前Thread内部的ThreadLocal.ThreadLocalMap,然后以ThreadLocal为key,从这个Map中获取对应的value就是结果

设计理念:

a. ThreadLocal中的数据实际存放于Thread中,线程死亡时,这些数据会被自动释放,减小了开销

b. 一般来说,一个ThreadLocal对应的Thread数量远多于一个Thread所对应的ThreadLocal数量,因此Thead内部维护的ThreadLocal.ThreadLocalMap的长度一般来说是较短的,寻址快速

存在的问题:

直接跟踪ThreadLocal.get()方法的调用链:

ThreadLocalMap.get()
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);//获取当前线程内部维护的ThreadLocalMap对象
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);//以当前TheadLocal为key,在ThreadLocalMap中查询数据
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

ThreadLocal.ThreadLocalMap.getEntry()
        /**
         * Get the entry associated with key.  This method
         * itself handles only the fast path: a direct hit of existing
         * key. It otherwise relays to getEntryAfterMiss.  This is
         * designed to maximize performance for direct hits, in part
         * by making this method readily inlinable.
         *
         * @param  key the thread local object
         * @return the entry associated with key, or null if no such
         */
        private Entry getEntry(ThreadLocal<?> key) {
            int i = key.threadLocalHashCode & (table.length - 1);//ThreadLocal的threadLocalHashCode是在定义ThreadLocal时产生的一个伪随机数,可以理解为ThreadLocal的hashCode,此处用其计算ThreadLocal在ThreadLocalMap中的下标
            Entry e = table[i];//寻址
            if (e != null && e.get() == key)//命中
                return e;
            else
                return getEntryAfterMiss(key, i, e);//未命中,目标地址上存储了另外一个ThreadLocal及其对应的value(hash碰撞)
        }

ThreadLocal.ThreadLocalMap.getEntryAfterMiss()
        private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
            Entry[] tab = table;
            int len = tab.length;

            while (e != null) {
                ThreadLocal<?> k = e.get();
                if (k == key)
                    return e;
                if (k == null)
                    expungeStaleEntry(i);//如果key为null,则删除对应的value(由于ThreadLocalMap中的Entry扩展于WeakReference,因此如果ThreadLocal没有强引用的情况下,ThreadLocal会被gc回收掉,此时key为空。为了便于gc,需要同时删除对value的引用)
                else
                    i = nextIndex(i, len);//查找ThreadLocalMap中的下一个元素,直到命中为止(很明确的线性探测法)
                e = tab[i];
            }
            return null;
        }

代码逻辑不算复杂,其问题在于在ThreadLocal.ThreadLocalMap中查找时,采用的是线性探测法,一般情况下时间复杂度是O(1),但是在发生哈希冲突时,可能会退化到O(n)的时间复杂度。

Netty中针对此处做出了下面的优化

 

2. Netty中的FastThreadLocal原理

原理简介:

a. FastThreadLocal的构造方法中,会为当前FastThreadLocal分配一个index,这个index是由一个全局唯一的static类型的AtomInteger产生的,可以保证每个FastThreadLocal的index都不同

b. FastThreadLocal需要与FastThreadLocalThread配套使用(FastThreadLocalThread内部维护了一个InternalThreadLocalMap类型的threadLocalMap属性,在调用FastThreadLocal的get方法时会去这个InternalThreadLocalMap中查询)

c. InternalThreadLocalMap内部也是使用数组作为底层存储,key为FastThreadLocal,寻址方式是直接使用FastThreadLocal内部维护的index,由于每个FastThreadLocal的index都不同,因此不会发生hash冲突,直接取数据即可,效率极高

d. 代价是有多少个FastThreadLocal对象, InternalThreadLocalMap内部就得开多大的底层数组(为了防止频繁扩容,实际上还要略大一点),也就是空间换时间了

源码分析:

//FastThreadLocal的构造方法中创建全局唯一的index的过程
    public FastThreadLocal() {
        index = InternalThreadLocalMap.nextVariableIndex();
    }

    static final AtomicInteger nextIndex = new AtomicInteger();
    public static int nextVariableIndex() {
        int index = nextIndex.getAndIncrement();
        if (index < 0) {
            nextIndex.decrementAndGet();
            throw new IllegalStateException("too many thread-local indexed variables");
        }
        return index;
    }

    
FastThreadLocal.get()
    public final V get() {
        return get(InternalThreadLocalMap.get());
    }

InternalThreadLocalMap.get()//先从当前线程中获取维护的InternalThreadLocalMap属性
    public static InternalThreadLocalMap get() {
        Thread thread = Thread.currentThread();
        if (thread instanceof FastThreadLocalThread) {
            return fastGet((FastThreadLocalThread) thread);
        } else {
            return slowGet();
        }
    }

InternalThreadLocalMap.fastGet()
    private static InternalThreadLocalMap fastGet(FastThreadLocalThread thread) {
        InternalThreadLocalMap threadLocalMap = thread.threadLocalMap();
        if (threadLocalMap == null) {
            thread.setThreadLocalMap(threadLocalMap = new InternalThreadLocalMap());
        }
        return threadLocalMap;
    }

FastThreadLocal.get()
    public final V get(InternalThreadLocalMap threadLocalMap) {
        Object v = threadLocalMap.indexedVariable(index);
        if (v != InternalThreadLocalMap.UNSET) {
            return (V) v;
        }

        return initialize(threadLocalMap);
    }

    public Object indexedVariable(int index) {
        Object[] lookup = indexedVariables;//indexedVariables就是InternalThreadLocalMap的底层数组了
        return index < lookup.length? lookup[index] : UNSET;
    }

逻辑还是比较清晰的,我就不再做解释了

 

3. 总结

Netty的FastThreadLocal与JDK自带的ThreadLocal的设计理念实际上还是相通的,都是在Thread的内部维护了一个Map,然后把数据记录在其中,只是Netty摒弃了JDK中采用的线性探测法,而是为每个FastThreadLocal对象生成一个全局唯一的index,并以此在Map中寻址,这样就直接解决了哈希冲突的问题。至于高性能与空间浪费的取舍,就见仁见智了。

posted @ 2017-10-14 16:25  qeDVuHG  阅读(1499)  评论(0编辑  收藏  举报