这里之所以分了 fastGet 和 slowGet 是为了做一个兼容,假设有个不熟悉的人,他用了 FastThreadLocal 但是没有配套使用 FastThreadLocalThread ,然后调用 FastThreadLocal#get 的时候去 Thread 里面找 InternalThreadLocalMap 那不就傻了吗,会报错的。
所以就再弄了个 slowThreadLocalMap ,它是个 ThreadLocal ,里面保存 InternalThreadLocalMap 来兼容一下这个情况。
从这里我们也能得知,FastThreadLocal 最好和 FastThreadLocalThread 配套使用,不然就隔了一层了。
FastThreadLocal<String> threadLocal = new FastThreadLocal<String>(); Thread t = new FastThreadLocalThread(new Runnable() { //记得要 new FastThreadLocalThread public void run() { threadLocal.get(); .... } });
好了,get 和 set 这两个核心操作都分析完了,我们最后再来看一下 remove 操作吧。
很简单对吧,把数组里的 value 给覆盖了,然后再到 set 里把对应的 FastThreadLocal 对象给删了。
不过看到这里,可能有人会发出疑惑,内存泄漏相关的点呢?
其实吧,可以看到 FastThreadLocal 就没用弱引用,所以它把无用 FastThreadLocal 的清理就寄托到规范使用上,即没用了就主动调用 remove 方法。
但是它曲线救国了一下,我们来看一下 FastThreadLocalRunnable 这个类:
我已经把重点画出来了,可以看到这个 Runnable 执行完毕之后,会主动调用 FastThreadLocal.removeAll() 来清理所有的 FastThreadLocal,这就是我说的曲线救国,怕你完了调用 remove ,没事我帮你封装一下,就是这么贴心。
当然,这个前提是你不能用 Runnable 而是用 FastThreadLocalRunnable。不过这里 Netty 也是做了封装的。
Netty 实现了一个 DefaultThreadFactory 工厂类来创建线程。
你看,你传入 Runnable 是吧,没事,我把它包成 FastThreadLocalRunnable,并且我 new 回去的线程是 FastThreadLocalThread 类型,这样就能在很大程度上避免使用的错误,也减少了使用的难度。
这也是工厂方法这个设计模式的好处之一啦。所以工程上如果怕对方没用对,我们就封装了再给别人使用,这样也屏蔽了一些细节,他好你也好。
所以说多看看开源框架的源码,有很多可以学习的地方!好了,FastThreadLocal 原理大致就说到这里。
FastThreadLocal VS ThreadLocal
到此,我们已经充分了解了两者之间的不同,但是 Fast 到底有多 Fast 呢?
我们用实验说话,Netty 源码里面已经有 benchmark 了,我们直接跑就行了
里面有两个实验:
FastPath 对应的是使用 FastThreadLocalThread 线程对象。
SlowPath 对应的是使用 Thread 线程对象。
两个实验都是分别定义了 ThreadLocal 和 FastThreadLocal :
可以看到搭配 FastThreadLocalThread 来使用 FastThreadLocal 吞吐确实比使用 ThreadLocal 大,但是好像也没大太多?
不过,我在网上有看别比人的 benchmark 对比,同样的代码,他的结果是大了三倍。
我反正又跑了几遍,每次都比原生的 ThreadLocal 吞吐好,但是也没好那么多...有点奇怪。
至于 FastThreadLocal 搭配 Thread 则吞吐比 ThreadLocal 都少,说明 FastThreadLocal 的使用必须得搭配 FastThreadLocalThread ,不然就是反向优化了。
代码在 netty 的 microbench 这个项目里,有兴趣的可以自己 down 下来跑一跑看看。
最后
我们再来总结一下:
- FastThreadLocal 通过分配下标直接定位 value ,不会有 hash 冲突,效率较高。
- FastThreadLocal 采用空间换时间的方式来提高效率。
- FastThreadLocal 需要配套 FastThreadLocalThread 使用,不然还不如原生 ThreadLocal。
- FastThreadLocal 使用最好配套 FastThreadLocalRunnable,这样执行完任务后会主动调用 removeAll 来移除所有 FastThreadLocal ,防止内存泄漏。
- FastThreadLocal 的使用也是推荐用完之后,主动调用 remove。
这就是 Netty 实现的加强版 ThreadLocal,如果你看过 Netty 源码,你会发现内部是有挺多使用 ThreadLocal 的场景,所以这个优化还是有必要的。
并且 Netty work 线程池默认线程数是两倍 CPU 核心数,所以线程不会太多,那么空间的浪费其实也不会很多,所以这波空间换时间影响不大。
好了,文章就到这了。挖个坑,我在 InternalThreadLocalMap 这个类里面发现了一些奇怪的 long 变量。
懂行的同学看着可能知道,这是为了填充 Cache Line,避免伪共享问题的产生。
ok ,那为什么被标记了@deprecated?并且说将来的版本要被移除?
且听下回分解。