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中寻址,这样就直接解决了哈希冲突的问题。至于高性能与空间浪费的取舍,就见仁见智了。