前言
netty学习系列笔记总结,性能优化工具类之FastThreadLocal源码浅析,错误之处欢迎指正, 共同学习
如何使用
1 |
|
1 | java.lang.Object 6cbcc |
构造方法解析
Netty重新设计了更快的FastThreadLocal,主要实现涉及
- FastThreadLocalThread
- FastThreadLocal
- InternalThreadLocalMap
FastThreadLocalThread是Thread类的简单扩展,主要是为了扩展threadLocalMap属性
FastThreadLocal提供的接口和传统的ThreadLocal一致,主要是set和get方法,用法也一致
不同地方在于FastThreadLocal的值是存储在InternalThreadLocalMap这个结构里面的,传统的ThreadLocal性能槽点主要是在读写的时候hash计算和当hash没有命中的时候发生的遍历
1 | public FastThreadLocal() { |
nextIndex是InternalThreadLocalMap父类的一个全局静态的AtomicInteger类型的对象,这意味着所有的FastThreadLocal实例将共同依赖这个指针来生成唯一的索引,而且是线程安全的;
InternalThreadLocalMap实例和Thread对象一一对应;
该index也是绑定的FastThreadLocal对象的value在Object[]数组中的索引位置
get方法解析
1.FastThreadLocal的获取
1 | //获取当前线程的InternalThreadLocalMap中的当前ftl的value |
2.获取InternalThreadLocalMap
1 | public final V get() { |
如果当前线程是ftlt线程,则使用fastGet进行获取;否则使用slowGet进行获取。
fastGet:
1 | //底层自己维护了一个ThreadLocalMap对象 |
如果该threadLocalMap已经实例化过,则直接返回,否则,先创建一个InternalThreadLocalMap实例,然后将该实例设置到ftlt的threadLocalMap属性中。
1 | /** |
slowGet:
1 | static final ThreadLocal<InternalThreadLocalMap> slowThreadLocalMap = new ThreadLocal<InternalThreadLocalMap>(); |
之所以成为slowGet的原因是因为:
fastGet可以直接从当前线程的属性获取;而slowGet需要根据slowThreadLocalMap的索引值与数组长度进行计算之后进行获取,如果没有直接根据索引命中的话,还可能需要进行线性探测的向后循环查找操作,当然还可能有一些清理和整理逻辑。
fastGet设置InternalThreadLocalMap,直接给当前线程的属性赋值,而slowGet的set操作需要使用线性探测法进行设置,并会至少执行一次log级别的资源回收整理操作
如上两点也是ftl比tl快的原因。但是可以看出tl在不断的回收无效的Entry使得新的Entry可以插入而不需要额外空间,但是ftl只能不断的增加index,不断向后增加,而index前边被remove掉的位置不能被重用,所以Object[]数组的size会越来越大,算是一种空间换时间的做法。
3.从InternalThreadLocalMap获取值
1 | Object[] indexedVariables; |
4.初始化操作
1 | private V initialize(InternalThreadLocalMap threadLocalMap) { |
如果索引小于indexedVariables.length,直接获取indexedVariables[index];否则,进行扩容设置。
首先获取旧数组及其长度;然后进行新数组容量的计算(计算方式与1.8的HashMap一样:都是获取比给定值大的最小的2的n次方的数);然后创建新数组并拷贝旧数组元素到新数组,最后对扩容多出来的元素初始化为UNSET,然后设置value值,最后将新数组赋值给indexedVariables成员变量。
到此为止设置值的操作就结束了,最后:添加当前的FastThreadLocal到InternalThreadLocalMap的Set<FastThreadLocal<?>>中
1 | private static void addToVariablesToRemove(InternalThreadLocalMap threadLocalMap, FastThreadLocal<?> variable) { |
这个方法的目的是将 FastThreadLocal 对象保存到一个 Set 中,因为 Netty 的 Map 只是一个数组,没有键,所以保存到一个 Set 中,这样就可以判断是否 set 过这个 map,例如 Netty 的 isSet 方法就是根据这个判断的。
5.注册资源清理器
1 | //当该ftl所在的线程不强可达时,清理其上当前ftl的value和set<FastThreadLocal<?>>中当前的ftl |
获取当前线程,如果当前线程是 FastThreadLocalThread 类型 且 cleanupFastThreadLocals 是 true,则返回 true,直接return。也就是说,Netty 线程池里面创建的线程都符合这条件,只有用户自定义的线程池不符合。 当然还有一个条件:如果这个 ftl 的 index + 1 在 map 中的值不是空对象,则已经注册过了,也直接 return,不再重复注册。
set方法解析
1 | /** |
remove方法解析
1 | //清除当前的FastThreadLocal |
removeAll方法解析
1 | public static void removeAll() { |
首先获取当前线程map,然后获取 Set,将 Set 转成数组,遍历数组,调用 ftl 的 remove 方法。最后,删除线程中 的 map 属性。
总结
ftl使用了单纯的数组操作来替代了tl的hash表操作,所以在高并发的情况下,ftl操作速度更快。
ftl直接根据index进行数组set,而tl需要先根据tl的hashcode计算数组下标(而ftl是直接获取),然后再根据线性探测法进行set操作,其间如果发生hash冲突且有无效的Entry时,还要进行Entry的清理和整理操作。最后不管是否冲突,都要进行一次log级别的Entry回收操作,所以慢了。
ftl相较于tl不好的地方就是内存占用大,不会重复利用已经被删除(用UNSET占位)的数组位置,只会一味增大,是典型的“空间换时间”的操作。