原来这就是比 ThreadLocal 更快的玩意?(下)

简介: 原来这就是比 ThreadLocal 更快的玩意?(下)

这里之所以分了 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 操作吧。


image.png


很简单对吧,把数组里的 value 给覆盖了,然后再到  set  里把对应的 FastThreadLocal 对象给删了。

不过看到这里,可能有人会发出疑惑,内存泄漏相关的点呢?

其实吧,可以看到 FastThreadLocal 就没用弱引用,所以它把无用 FastThreadLocal 的清理就寄托到规范使用上,即没用了就主动调用 remove 方法。

但是它曲线救国了一下,我们来看一下 FastThreadLocalRunnable 这个类:


image.png


我已经把重点画出来了,可以看到这个 Runnable 执行完毕之后,会主动调用 FastThreadLocal.removeAll() 来清理所有的 FastThreadLocal,这就是我说的曲线救国,怕你完了调用 remove ,没事我帮你封装一下,就是这么贴心。

当然,这个前提是你不能用 Runnable 而是用 FastThreadLocalRunnable。不过这里 Netty 也是做了封装的。

Netty 实现了一个 DefaultThreadFactory 工厂类来创建线程。


image.png


你看,你传入 Runnable 是吧,没事,我把它包成 FastThreadLocalRunnable,并且我 new 回去的线程是 FastThreadLocalThread 类型,这样就能在很大程度上避免使用的错误,也减少了使用的难度。

这也是工厂方法这个设计模式的好处之一啦。所以工程上如果怕对方没用对,我们就封装了再给别人使用,这样也屏蔽了一些细节,他好你也好

所以说多看看开源框架的源码,有很多可以学习的地方!好了,FastThreadLocal 原理大致就说到这里。


FastThreadLocal VS ThreadLocal


到此,我们已经充分了解了两者之间的不同,但是 Fast 到底有多 Fast 呢?

我们用实验说话,Netty 源码里面已经有 benchmark 了,我们直接跑就行了

里面有两个实验:

FastPath 对应的是使用 FastThreadLocalThread 线程对象。

SlowPath 对应的是使用 Thread 线程对象。


image.png


两个实验都是分别定义了 ThreadLocal 和 FastThreadLocal :



image.png


image.png


可以看到搭配 FastThreadLocalThread 来使用 FastThreadLocal 吞吐确实比使用 ThreadLocal 大,但是好像也没大太多?

不过,我在网上有看别比人的 benchmark 对比,同样的代码,他的结果是大了三倍。


image.png


我反正又跑了几遍,每次都比原生的 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 变量。


image.png


懂行的同学看着可能知道,这是为了填充 Cache Line,避免伪共享问题的产生。

ok ,那为什么被标记了@deprecated?并且说将来的版本要被移除?

且听下回分解。

推荐阅读:我把 ThreadLocal 能问的,都写了


相关文章
|
6月前
|
存储 分布式计算 安全
我的C++奇迹之旅:值和引用的本质效率与性能比较2
我的C++奇迹之旅:值和引用的本质效率与性能比较
|
6月前
|
编译器 C++
我的C++奇迹之旅:值和引用的本质效率与性能比较1
我的C++奇迹之旅:值和引用的本质效率与性能比较
|
数据可视化 Java Python
join()方法的神奇用处与Intern机制的软肋
照例先总结下本文内容:(1)join() 方法除了在拼接字符串时速度较快,它还是目前看来最通用有效的复制字符串的方法 (2)Intern 机制(字符串滞留)并非万能的,本文探索一下它的软肋有哪些
159 0
join()方法的神奇用处与Intern机制的软肋
|
存储 缓存 NoSQL
没弄懂深浅拷贝你也敢用缓存?
而其中,最简单的肯定就是第一种服务内部的缓存了。强哥前些天就是用了Guava做了缓存,结果因为代码写的有些乱,就出了个匪夷所思的问题。搞了半天最后才发现问题所在。在这里和大家分享一哈。
没弄懂深浅拷贝你也敢用缓存?
|
存储 算法 Java
原来这就是比 ThreadLocal 更快的玩意?(上)
原来这就是比 ThreadLocal 更快的玩意?(上)
原来这就是比 ThreadLocal 更快的玩意?(上)
原来这就是比 ThreadLocal 更快的玩意?(中)
原来这就是比 ThreadLocal 更快的玩意?(中)
原来这就是比 ThreadLocal 更快的玩意?(中)
ThreadLocal不好用?那是你没用对!(10)
ThreadLocal不好用?那是你没用对!(10)
89 0
ThreadLocal不好用?那是你没用对!(10)
ThreadLocal不好用?那是你没用对!(14)
ThreadLocal不好用?那是你没用对!(14)
101 0
ThreadLocal不好用?那是你没用对!(14)
|
安全
ThreadLocal不好用?那是你没用对!(4)
ThreadLocal不好用?那是你没用对!(4)
113 0
ThreadLocal不好用?那是你没用对!(4)
ThreadLocal不好用?那是你没用对!(2)
ThreadLocal不好用?那是你没用对!(2)
97 0
ThreadLocal不好用?那是你没用对!(2)