Netty的FastThreadLocal快在哪
Netty的FastThreadLocal快在哪
Netty 不仅是高性能的网络编程框架,也有很多优秀的设计,譬如本篇要介绍的 FastThreadLocal。
FastThreadLocal 是相比 ThreadLocal 来讲的,那它究竟 fast 在哪?
ThreadLocal
实现
ThreadLocal 可以用来实现线程内单例。
对于线程内单例,简单的实现可以维护一个 ConcurrentHashMap,其中 Key 是 Thread 对象,Value 是 ThreadLocal 的类型。ThreadLocal 不是这么实现的,因为在高并发的场景并发修改 Map 如果需要加锁,势必会降低性能。
JDK 为了避免加锁,采用了相反的设计思路。JDK 在每个 Thread 内维护一个 Map,Map 的元素称为 Entry,Map 的 Key 是 ThreadLocal,Value 是这个 ThreadLocal 的类型的值。

1 | |
即该线程内的 ThreadLocalMap,会有一个 Entry 元素 <threadLocal, 1>。
1 | |
ThreadLocalMap 是一种使用线性探测法实现的哈希表,底层采用数组存储数据。当发生哈希冲突时,要向后寻找空闲的位置,当冲突频率很高时,解决哈希冲突的成本很大,而且在查找数据时也需要向后查找(哈希值对应的位置可能被其它节点线性探测占用了)。当容量达到阈值时,需要进行扩容,扩容后需要再哈希确定元素位置。
内存泄漏
提起 ThreadLocal 也不得不提内存泄漏,系统中为了避免频繁的创建销毁线程,一般都会使用线程池复用线程。想一下下面的场景:
- 线程在执行过程中向某个方法内创建的 ThreadLocal 对象中设置了值;
- 方法执行结束,线程回收到线程池;
- 由于线程内的 ThreadLocalMap 中引用了第一步的 ThreadLocal 对象,所以即使方法执行结束,ThreadLocal 对象也无法被回收。
JDK 为了避免这样的情况,做了以下措施:
- ThreadLocalMap 的 Key 采用弱引用类型,即当没有强引用时只要发生 gc 就会回收;但只有 Key 时弱引用,Value 依然是强引用;
- 在执行 ThreadLocal.set()/get() 方法时,ThreadLocal 会清除 ThreadLocalMap 中 key 为 NULL 的 Entry 对象,让它能够被 GC 回收。
但是为了避免内存泄漏,还是要求某个 ThreadLocal 对象不再使用时,立即调用 remove() 方法删除 Entry 对象。
FastThreadLocal
通过对 ThreadLocal 的介绍,它有以下缺点:
- 解决哈希冲突的成本大,可能会存在性能瓶颈;
- 如果不及时 remove,可能产生内存泄漏;
实现
FastThreadLocalThread 是对 Thread 类的一层包装,每个线程对应一个 InternalThreadLocalMap 实例。只有 FastThreadLocal 和 FastThreadLocalThread 组合使用时,才能发挥 FastThreadLocal 的性能优势。
InternalThreadLocalMap 与 ThreadLocalMap 的实现差别很大,虽然也是采用数组的方式存储元素,但它可以做到 O(1) 的访问速度,而且只存储 Value,原因就藏在源码里:
1 | |
在创建 FastThreadLocal 时,会为其中的 index 属性赋一个值,赋值的逻辑很简单:
1 | |
也就是每一个 FastThreadLocal 都有唯一的一个 index,那在 Thread 中存储 FastThreadLocal值 的时候,直接存到 index 对应的位置不就直接解决哈希冲突了吗?
1 | |
当数组容量满时,需要根据 index 的值进行扩容(注意这里不能是简单的 2 倍关系,例如当前长度是 32,但是插入了一个 index 是 72 的 FastThreadLocal,那么扩容2倍是不够的),但这并不会浪费很多空间,那些没使用的位置只是存放了同一个缺省对象的引用。
内存泄漏
FastThreadLocalThread 对于内存泄漏又有什么改进呢?
Netty 通过 DefaultThreadFactory 创建的所有线程都是 FastThreadLocalThread(Netty 的 EventLoop 就是 FastThreadLocalThread),并且所有提交的 Runnable 任务会被包装成 FastThreadLocalRunnable。
当一个线程(特别是线程池中的线程)执行完任务后,其实就可以清理当前线程上绑定的所有 FastThreadLocal 变量了。在 FastThreadLocalThread 的设计中,如果一个 index 被设置了值,那么这个 FastThreadLocal 就会放到一个 Set 中,这个 Set 就存在 indexedVariables[0] 的位置。
1 | |
Netty 还封装了 FastThreadLocalRunnable,FastThreadLocalRunnable 最后会执行 FastThreadLocal.removeAll() 将 Set 集合中所有 FastThreadLocal 对象都清理掉。
1 | |
小结
相比 JDK 原生的 ThreadLocal,Netty 的 FastThreadLocal 一方面提高了并发情况下的性能,也优化了内存泄漏。
但 FastThreadLocal 不一定比 ThreadLocal 快,只有是 FastThreadLocalThread 类型的线程才会更快,如果是普通线程反而会更慢。