FastThreadLocal
概述: ThreadLocal的一个特定变种改善,有更好的存取性能。
内部采用一个数组来代替ThreadLocal内部的hash表来存放变量。虽然这看起来是微不足道的,但是他确实比hash表性能好那么一点,在频繁存取时会更明显。 如果用DefaultThreadFactory创建线程,那么默认创建出来的就是FastThreadLocalThread,就会用FastThreadLocal。
set数据靠InternalThreadLocalMap维护,InternalThreadLocalMap内部靠一个数组(就是上面说的)维护变量数据。
扩展了什么:
按ThreadLocal API的约定行为,依赖InternalThreadLocalMap实现了这些行为,诸如get、set、remove等。
remove支持onRemoval回调。
InternalThreadLocalMap
自身实例获取
get方法是对外暴露去自身实例的,有两种方式取到InternalThreadLocalMap实例:
- 如果当前线程是FastThreadLocalThreadInternal,直接取其实例变量ThreadLocalMap,内部称之为fastGet。
- 如果是JDK的Thread,那么靠JDK的TheadLocal取到ThreadLocalMap,内部称之为slowGet。
数据存取
真正的存取变量是靠indexedVariable
和setIndexedVariable
方法完成。
阅读代码不难发现,是靠Object[] indexedVariables这个数组达成数据存储的目的。
存放数据的数组扩容
indexedVariables数组靠expandIndexedVariableTableAndSet动态扩容。初始长度是32。
扩容算法有点意思,是比当前index小的最大的2的n次方的值扩一倍,比如当前index是132,那么就会扩成256长度的数组。
Object[] oldArray = indexedVariables;
final int oldCapacity = oldArray.length;
int newCapacity = index;
newCapacity |= newCapacity >>> 1;
newCapacity |= newCapacity >>> 2;
newCapacity |= newCapacity >>> 4;
newCapacity |= newCapacity >>> 8;
newCapacity |= newCapacity >>> 16;
newCapacity ++;
是否完全用数组存放数据?
不完全是。因为InternalThreadLocalMap
的父类UnpaddedInternalThreadLocalMap
自带了一些常用的字段:
- futureListenerStackDepth
- localChannelReaderStackDepth
- handlerSharableCache
- counterHashCode
- random
- typeParameterMatcherGetCache
- typeParameterMatcherFindCache
- stringBuilder
- charsetEncoderCache
- charsetDecoderCache
- arrayList
这个11个是靠实例字段直接存储。
另外此类,还用了padding补齐的手段优化了CPU cacheline伪共享的问题。我猜测性能提升主要来源于此。
// Cache line padding (must be public)
// With CompressedOops enabled, an instance of this class should occupy at least 128 bytes.
public long rp1, rp2, rp3, rp4, rp5, rp6, rp7, rp8, rp9;
关于padding补齐
该类为了解决cache line伪共享的问题,采用了padding补齐。
该类(4.1.32.Final版本)补齐后通过sizeOfObject(也可以用jol)算出来大小是136。 jol针对idea是有插件的,不像JDK带的jol要运行起来才能计算对象大小。idea那个插件是针对语法树分析后算的,因为就算你的类有编译错误,他也能算出来。使用时注意选择相应的压缩模式,右上角。
关于jol有官方的sample是很不错的,还有个博客写的还行,包括字段重排等都有。
为什么是136?按说128就行啊? 这个事情我之前也思索了好久没有答案。直至翦哥今天跟我提到了一个别人前几天提的issue,才翻到原来有人和我们有一样的困惑,而且答案竟然是在netty版本迭代过程中InternalThreadLocalMap的父类加了个字段:ArrayList