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

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

你好,我是yes。

继上一篇之后我把 ThreadLocal 能问的,都写了之后,咱们再来盘一盘 FastThreadLocal ,这个算是 ThreadLocal 的进阶版,是 Netty 针对 ThreadLocal 自己造的轮子,所以对 ThreadLocal 没有完全理解的话,建议先看上一篇文章,打个基础。

那了解 FastThreadLocal 之后呢,对平日的一些优化可能可以提供一些思路,或者面试就能装个x。

面试官:ThreadLocal 竟然有xxx这个缺点,那怎么优化啊?

你就把 FastThreadLocal 的实现 BB 一遍,这不就稳妥了嘛!

所以,今天我们就来看看 Netty 是如何实现 FastThreadLocal 的,话不多说,本文大纲如下:

  • 数数 ThreadLocal 的缺点。
  • 应该如何针对 ThreadLocal 缺点改进?
  • FastThreadLocal 的原理。
  • FastThreadLocal VS ThreadLocal 的实操。

这篇下来,进阶版 ThreadLocal 基本拿下,下篇我会基于这篇做一个延伸,一个比较底层的延伸,属于绝对装x的那种,等下看文章你就知道了,我会埋坑的,哈哈。

发车发车!


数数 ThreadLocal 的缺点


看完上篇文章的同学,应该都很清楚了 ThreadLocal 的一个缺点:hash 冲突用的是线性探测法,效率低。


image.png


可以看到,图上显示的是经过两个遍历找到了空位,假设冲突多了,需要遍历的次数就多了。并且下次 get 的时候,hash 直接命中的位置发现不是要找的 Entry ,于是就接着遍历向后找,所以说这个效率低。

而像 HashMap 是通过链表法来解决冲突,并且为了防止链表过长遍历的开销变大,在一定条件之后又会转变成红黑树来查找,这样的解决方案在频繁冲突的条件下,肯定是优于线性探测法,所以这是一个优化方向。

不过 FastThreadLocal  不是这样优化的,我们下面再说。

还有一个缺点是 ThreadLocal 使用了 WeakReference 以保证资源可以被释放,但是这可能会产生一些 Etnry 的 key 为 null,即无用的 Entry 存在。

所以调用 ThreadLocal 的 get 或 set 方法时,会主动清理无用的 Entry,减轻内存泄漏的发生。


image.png


这其实等于把清理的开销弄到了 get 和 set 上,万一 get 的时候清理的无用 Entry 特别多,那这次 get 相对而言就比较慢了。

还有一个就是内存泄漏的问题了,当然这个问题只存在于用线程池使用的时候,并且上面也提到了 get 和 set 的时候也能清理一些无用的 Key,所以没有那么的夸张,只要记得用完后调用 ThreadLocal#remove 就不会有内存泄漏的问题了。

大致就这么几点。


应该如何针对 ThreadLocal 缺点改进


所以怎么改呢?

前面提到 ThreadLocal hash 冲突的线性探测法不好,还有 Entry 的弱引用可能会发生内存泄漏,这些都和 ThreadLocalMap 有关,所以需要搞个新的 map 来替换 ThreadLocalMap。

而这个 ThreadLocalMap 又是 Thread 里面的一个成员变量,这么一看 Thread 也得动一动,但是我们又无法修改 Thread 的代码,所以配套的还得弄个新的 Thread。

所以我们不仅得弄个新的 ThreadLocal、ThreadLocalMap 还得弄个配套的 Thread 来用上新的 ThreadLocalMap 。

所以如果想改进 ThreadLocal ,就需要动这三个类。

对应到 Netty 的实现就是 FastThreadLocal、InternalThreadLocalMap、FastThreadLocalThread


image.png


然后发散一下思维,既然 Hash 冲突的想线性探测效果不好,你可能比较容易想到的就是上面提到的链表法,然后再基于链表法说个改成红黑树,这个确实是一方面,但是可以再想想。

比如,让 Hash 不冲突,所以设计一个不会冲突的 hash 算法?不存在的!

所以怎么样才不会产生冲突呢?


各自取号入座

什么意思?就是每往 InternalThreadLocalMap 中塞入一个新的 FastThreadLocal 对象,就给这个对象发个唯一的下标,然后让这个对象记住这个下标,到时候去 InternalThreadLocalMap 找 value 的时候,直接通过下标去取对应的 value 。

这样不就不会冲突了?

这就是 FastThreadLocal 给出的方案,具体下面分析。

还有个内存泄漏的问题,这个其实只要规范的使用即用完后 remove 就好了,其实也没太好的解决方案,不过 FastThreadLocal 曲线救国了一下,这个也且看下面的分析!


FastThreadLocal 的原理


以下 Netty 基于 4.1 版本分析

先来看下 FastThreadLocal 的定义:


image.png

可以看到有个叫 variablesToRemoveIndex 的类成员,并且用 final 修饰的,所以等于每个 FastThreadLocal 都有个共同的不可变 int 值,值为多少等下分析。

然后看到这个 index 没,在 FastThreadLocal 构造的时候就被赋值了,且也被 final 修饰,所以也不可变,这个 index 就是我上面说的给每个新 FastThreadLocal 都发个唯一的下标,这样每个 index 就都知道自己的位置了。

上面两个 index 都是通过 InternalThreadLocalMap.nextVariableIndex() 赋值的,盲猜一下,这个肯定是用原子类递增实现的。

我们来看一下实现:


image.png

确实,在 InternalThreadLocalMap 也定义了一个静态原子类,每次调用 nextVariableIndex 就返回且递增,没有什么别的赋值操作,从这里也可以得知 variablesToRemoveIndex 的值为 0,因为它属于常量赋值,第一次调用时 nextIndex 的值为 0 。

看到这,不知道大家是否已经感觉到一丝不对劲了。好像有点浪费空间的意思,我们继续往下看。

InternalThreadLocalMap 对标的就是之前的 ThreadLocalMap 也就是 ThreadLocal 缺点集中的类,需要重点看下。

我们再来回顾一下 ThreadLocalMap 的定义。


image.png

它是个 Entry 数组,然后 Entry 里面弱引用了 ThreadLocal 作为 Key。

而 InternalThreadLocalMap 有点不太一样:


image.png


可以看到, InternalThreadLocalMap 好像放弃了 map 的形式,没用定义 key 和 value,而是一个 Object 数组?

那它是如何通过 Object 来存储  FastThreadLocal 和对应的 value 的呢?我们从 FastThreadLocal#set 开始分析:


image.png


因为我们已经熟悉 ThreadLocal 的套路,所以我们知道 InternalThreadLocalMap 肯定是 FastThreadLocalThread 里面的一个变量。

然后我们从对应的 FastThreadLocalThread 里面拿到了 map 之后,就要执行塞入操作即 setKnownNotUnset

我们先看一下塞入操作里面的 setIndexedVariable 方法:

image.png



相关文章
|
3月前
|
安全 Java 编译器
揭秘JAVA深渊:那些让你头大的最晦涩知识点,从泛型迷思到并发陷阱,你敢挑战吗?
【8月更文挑战第22天】Java中的难点常隐藏在其高级特性中,如泛型与类型擦除、并发编程中的内存可见性及指令重排,以及反射与动态代理等。这些特性虽强大却也晦涩,要求开发者深入理解JVM运作机制及计算机底层细节。例如,泛型在编译时检查类型以增强安全性,但在运行时因类型擦除而丢失类型信息,可能导致类型安全问题。并发编程中,内存可见性和指令重排对同步机制提出更高要求,不当处理会导致数据不一致。反射与动态代理虽提供运行时行为定制能力,但也增加了复杂度和性能开销。掌握这些知识需深厚的技术底蕴和实践经验。
78 2
|
5月前
|
存储 安全 Java
彻底攻克ThreadLocal:搞懂原理、实战应用,深挖源码!扩展InheritableThreadLocal、FastThreadLocal!
彻底攻克ThreadLocal:搞懂原理、实战应用,深挖源码!扩展InheritableThreadLocal、FastThreadLocal!
|
Web App开发 JavaScript 前端开发
前端优化的几种方法和底层原理
前端优化的几种方法和底层原理
103 0
|
前端开发 JavaScript 开发工具
🎖️我只会使用前端框架开发不行吗?
我们经常看到一个现象:一些前端开发人员可能会在使用最新框架创建漂亮的网站的同时,却在编写基本的CSS样式或创建简单的JavaScript函数方面感到困难。
110 0
|
缓存 负载均衡 Oracle
面试官:说下你在项目中是如何处理高并发的???
面试官:说下你在项目中是如何处理高并发的???
451 0
面试官:说下你在项目中是如何处理高并发的???
|
缓存 网络协议 算法
网络开销是什么意思?底层原理是什么?
网络开销是什么意思?底层原理是什么?
2341 0
|
数据可视化 Java Python
join()方法的神奇用处与Intern机制的软肋
照例先总结下本文内容:(1)join() 方法除了在拼接字符串时速度较快,它还是目前看来最通用有效的复制字符串的方法 (2)Intern 机制(字符串滞留)并非万能的,本文探索一下它的软肋有哪些
159 0
join()方法的神奇用处与Intern机制的软肋
|
JSON 算法 测试技术
接口测试平台174:并发底层(顺便谈谈俩个版本区别)
接口测试平台174:并发底层(顺便谈谈俩个版本区别)
接口测试平台174:并发底层(顺便谈谈俩个版本区别)
|
设计模式 Java C++
原来这就是比 ThreadLocal 更快的玩意?(下)
原来这就是比 ThreadLocal 更快的玩意?(下)
原来这就是比 ThreadLocal 更快的玩意?(下)
原来这就是比 ThreadLocal 更快的玩意?(中)
原来这就是比 ThreadLocal 更快的玩意?(中)
原来这就是比 ThreadLocal 更快的玩意?(中)
下一篇
无影云桌面