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

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

可以看到,根据传入构造 FastThreadLocal 生成的唯一 index 可以直接从 Object 数组里面找到下标并且进行替换,这样一来压根就不会产生冲突,逻辑很简单,完美。

那如果塞入的 value 不是 UNSET(默认值),则执行 addToVariablesToRemove 方法,这个方法又有什么用呢?


image.png


这就是 Object 数组的核心关系图了,第一个位置放了一个 set ,set 里面存储了所有使用的 FastThreadLocal 对象,然后数组后面的位置都放 value。

那为什么要放一个 set 保存所有使用的 FastThreadLocal 对象?

用于删除,你想想看,假设现在要清空线程里面的所有 FastThreadLocal ,那必然得有一个地方来存放这些 FastThreadLocal 对象,这样才能找到这些家伙,然后干掉。

所以刚好就把数组的第一个位置腾出来放一个 set 来保存这些 FastThreadLocal 对象,如果要删除全部 FastThreadLocal 对象的时候,只需要遍历这个 set ,得到 FastThreadLocal 的 index 找到数组对应的 位置将 value 置空,然后把 FastThreadLocal 从 set 中移除即可。

刚好 FastThreadLocal 里面实现了这个方法,我们来看下:


image.png


内容可能有点多了,我们做下小结,理一理上面说的:

首先 InternalThreadLocalMap 没有采用 ThreadLocalMap k-v形式的存储方式,而是用 Object 数组来存储 FastThreadLocal 对象和其 value,具体是在第一个位置存放了一个包含所使用的 FastThreadLocal 对象的 set,然后后面存储所有的 value。

之所以需要个 set 是为了存储所有使用的 FastThreadLocal 对象,这样就能找到这些对象,便于后面的删除工作。

之所以数组其他位置可以直接存储 value ,是因为每个 FastThreadLocal 构造的时候已经被分配了一个唯一的下标,这个下标对应的就是 value 所处的下标。

看到这里,不知道大家是否有感受到空间的浪费?

我举个例子。

假设系统里面一个 new 了 100 个 FastThreadLocal ,那第 100 个 FastThreadLocal 的下标就是 100 ,这个应该没有疑义。

从上面的 set 方法可以得知,只有调用 set 的时候,才会从当前线程中拿出 InternalThreadLocalMap ,然后往这个 map 的数组里面塞入 value,这里我们再回顾一下 set 的方法。


image.png


那这里是什么意思呢?

如果我这个线程之前都没塞过 FastThreadLocal ,此时要塞入第一个 FastThreadLocal ,构造出来的数组长度是32,但是这个 FastThreadLocal 的下标已经涨到了 100 了,所以这个线程第一次塞值,也仅仅只有这么一个值,数组就需要扩容

看到没,这就是我所说的浪费,空间被浪费了。

Netty 相关实现者知道这样会浪费空间,所以数组的扩容是基于 index 而不是原先数组的大小,你看看如果是基于原先数组的扩容,那么第一次扩容 2 倍,32 变成 64,还是塞不下下标 100 的数据,所以还得扩容一次,这就不美了。

所以可以看到扩容传进去的参数是 index 。


image.png

可以看到,直接基于 index 的向上 2 次幂取整。然后就是扩容的拷贝,这里是直接进行数组拷贝,不需要进行 rehash,而 ThreadLocalMap 的扩容需要进行rehash,也就是重新基于 key 的 hash 值进行位置的分配,所以这个也是 FastThreadLocal 优于ThreadLocal 的一个点

对了,上面那个向上 2 次幂取整的操作,不知道你们熟悉不熟悉,这个和 HashMap 的实现是一致的。


image.png


咳咳,但是我没有证据,只能说优秀的代码,就是源远流长

所以从上面的实现可以得知 Netty 就是特意这样设计的,用多余的空间去换取不会冲突的 set 和 get ,这样写入和获取的速度就更快了,这就是典型的空间换时间

好了,想必此时你已经弄懂了 FastThreadLocal 的核心原理了,我们再来看看 get 方法的实现,我想你应该能脑补这个实现了。


image.png


是吧,没啥难度,index 就是 FastThreadLocal 构造时候预先分配好的那个下标,然后直接进行一个数组下标查找,如果没找到就调用 init 方法进行初始化。

我们这里再继续探究一下InternalThreadLocalMap.get(),这里面做了一个兼容。不过我要先介绍一下 FastThreadLocalThread ,就是这玩意替代了  Thread。


image.png


可以看到它继承了 Thread ,并且弄了一个成员变量就是我们前面说的 InternalThreadLocalMap。

然后我们再来看一下 get 方法,我截了好几个,不过逻辑很简单。


image.png



相关文章
|
2月前
|
JavaScript 前端开发 Java
打造高效对象:编程秘籍与代码实操
打造高效对象:编程秘籍与代码实操
8 0
|
9月前
|
Web App开发 JavaScript 前端开发
前端优化的几种方法和底层原理
前端优化的几种方法和底层原理
|
9月前
|
前端开发 JavaScript 开发工具
🎖️我只会使用前端框架开发不行吗?
我们经常看到一个现象:一些前端开发人员可能会在使用最新框架创建漂亮的网站的同时,却在编写基本的CSS样式或创建简单的JavaScript函数方面感到困难。
66 0
|
12月前
|
程序员
【编程】程序的局部性原理对代码效率的影响
【编程】程序的局部性原理对代码效率的影响
84 0
|
安全 Java
面试官:HashMap 中 modCount 变量有什么作用?大部分人都理解错了。。
面试官:HashMap 中 modCount 变量有什么作用?大部分人都理解错了。。
158 0
面试官:HashMap 中 modCount 变量有什么作用?大部分人都理解错了。。
|
数据可视化 Java Python
join()方法的神奇用处与Intern机制的软肋
照例先总结下本文内容:(1)join() 方法除了在拼接字符串时速度较快,它还是目前看来最通用有效的复制字符串的方法 (2)Intern 机制(字符串滞留)并非万能的,本文探索一下它的软肋有哪些
138 0
join()方法的神奇用处与Intern机制的软肋
|
JSON 算法 测试技术
接口测试平台174:并发底层(顺便谈谈俩个版本区别)
接口测试平台174:并发底层(顺便谈谈俩个版本区别)
接口测试平台174:并发底层(顺便谈谈俩个版本区别)
|
存储 缓存 NoSQL
没弄懂深浅拷贝你也敢用缓存?
而其中,最简单的肯定就是第一种服务内部的缓存了。强哥前些天就是用了Guava做了缓存,结果因为代码写的有些乱,就出了个匪夷所思的问题。搞了半天最后才发现问题所在。在这里和大家分享一哈。
没弄懂深浅拷贝你也敢用缓存?
|
存储 算法 Java
原来这就是比 ThreadLocal 更快的玩意?(上)
原来这就是比 ThreadLocal 更快的玩意?(上)
原来这就是比 ThreadLocal 更快的玩意?(上)
|
设计模式 Java C++
原来这就是比 ThreadLocal 更快的玩意?(下)
原来这就是比 ThreadLocal 更快的玩意?(下)
原来这就是比 ThreadLocal 更快的玩意?(下)