FastThreadLocal是不是真的快?
小轲

什么是FastThreadLocal?

FastThreadLocal是Netty中对JDK提供的ThreadLocal优化改造版本,从名称上来看,它应该比ThreadLocal更快了,以应对Netty处理并发量大、数据吞吐量大的场景。

FastThreadLocal 是 ThreadLocal 的一种特殊变体,只有在使用 FastThreadLocalThread 线程类访问时,才能获得更高的访问性能。

体现了程序员的一种极致“Geek精神”,连多循环几次都不愿意,必须做到最快。

image

概括来讲,FastThreadLocal快的核心其实是一个index。有了这个index,在get和remove方法的时候,就直接通过下标获取。可以看到下图中的get方法,拿对象是直接通过index获取的。

源码分析

源码分析从三个地方进行分析,new FastThreadLocal() 、fastThreadLocal.get()、fastThreadLocal.set(value)来进行分析,从增删改查来看它的实现。

new() -分配当前对象的index索引

FastThreadLocal() 函数

其实这一步,是为这个对象申请InternalThreadLocalMap索引的过程,相当于注册当前对象,得到一个唯一id

image

nextVariableIndex() 申请索引

单线程中都存在一个InternalThreadLocalMap实例,用来区分线程变量。这一步跟ThreadLocal很像。

但是要保证绝对的线程安全,所以使用了CAS来进行比较并交换,保证了可靠性

image

get() - 从FastThreadLocal中获取值

get这一步看起来也比较简单,就是在new这个对象时获得这个索引,去用这个index查找数据

image

InternalThreadLocalMap#get() 获得当前线程的InternalThreadLocalMap

这一步存在一个关键限制:必须使用 Netty 封装的线程类,普通 Thread 无法使用 FastThreadLocal

image

InternalThreadLocalMap#indexedVariable() 通过下标索引读取内容

这一步其实更明了,就是通过先前new对象出来的index,来get这个InternalThreadLocalMap中数组的值,O(1)的时间复杂度即可

image

set() 按照分配索引,放入该线程的数组中

set 操作其实就是根据分配好的 index,将值存入当前线程的数组中。下面的remove()逻辑其实是netty自己的清理逻辑,不需要我们处理

imagesetKnownNotUnset(threadLocalMap, value) 往数组中放入值

这一步其实就是拿当前被分配到的index,去调用当前线程内InternalThreadLocalMap的数组直接赋值。

image

threadLocalMap.setIndexedVariable(index, value) 真正执行的地方

到这,进行一个可能会有old值替换过程,这一步是netty自己的业务逻辑,我们无需关注。

image

总结

为啥说是个噱头?

  • ✅ 在 Netty 这种 IO 框架里,它是真的快。
  • ❌ 自己搞不合适
  • 为什么?
    • 线程是自己控制的(全用 FastThreadLocalThread
    • 用到的变量多,访问频率高
    • 线程复用频繁,GC 敏感

是否真的变快了?

  • 变快肯定是变快的,因为并不需要有ThreadLocalMap的线性探测,直接O(1)就可以取到数据
  • 实现方式总结一下就是
    • 每个 FastThreadLocal 实例会分配一个全局唯一的 index(自增)
    • 每个线程维护一个 InternalThreadLocalMap,本质上是个 Object[]
    • 每个变量都直接按 index 存到 values[index]
  • 老的实现方式就是
    • 每一个线程都有一个ThreadLocalMap
    • 在get、set时,这个Map就根据当前key来往map中放入值
      • 当前key就是这个ThreadLocal对象,例UserThreadLocalTraceThreadLocal
    • 如果遇到hash冲突了怎么办
      • 会进行线性查找,这也就是netty不满意的地方,怕万一线程中有几十个ThreadLocal的情况下,会有hash撞槽的可能,带来额外的性能开销。(其实就是多循环几次)
  • 本质上,就是一个是数组,一个是map。一个遇到冲突会线性的从头找到尾,另一个天然隔绝了这种问题的存在。只是用起来比较费劲,需要是专门的FastThreadLocalThread对象

是否真的存在自动清理?

没有,在Netty框架内确实实现了自动清理,但是我们自己使用的话还是老样子要调用remove()函数

对照

对比点 ThreadLocal FastThreadLocal
存储结构 ThreadLocalMap(哈希表) 数组(按 index 存取)
是否有冲突 有,线性探测 无,直接数组定位
清理机制 没有自动清理 支持 removeAll(),但需手动调用
性能(变量多时) 探测成本高,容易卡 始终 O(1),访问极快
使用门槛 低,开箱即用 高,需要特殊线程配合
 评论
评论插件加载失败
正在加载评论插件