所以,其实线程上绑定的数据都是放到 InternalThreadLocalMap 里面的,不管你操作什么 ThreadLocal,实际上都是操作的 InternalThreadLocalMap。
那问题来了,你觉得一个叫做 fastGet ,一个叫做 slowGet。这个快慢,指的是 get 什么东西的快慢?
对咯,就是获取 InternalThreadLocalMap。
InternalThreadLocalMap 在 InternalThread 里面是一个变量维护的,可以直接通过 InternalThread.threadLocalMap() 获得:
标号为 ① 的地方是获取,标号为 ② 的地方是设置。
都是一步到位,操作起来非常的方便。
这是 fastGet。
而 slowGet 是从 ThreadLocal 中获取:
这里的 get ,就是原生 ThreadLocal 的 get 方法,一眼望去,就复杂多了:
标号为 ① 的地方,首先计算 hash 值,然后拿着 hash 值去数组里面取数据。如果取出来的数据不是我们想要的数据,则到标号为 ② 的逻辑里面去。
那么我问你,除了这个位置上的值真的为 null 外,还有什么原因会导致我拿着计算出来的 hash 值去数组里面取数据取不到?
就是看你熟不熟悉 ThreadLocal 对 hash 冲突的处理方式了。
那么这个问题稍微的升级一下就是:你知道哪些 hash 冲突的解决方案呢?
1.开放定址法。
2.链式地址法。
3.再哈希法。
4.建立公共溢出区。
我们非常熟悉的 HashMap 就是采用的链式地址法解决 hash 冲突。
而 ThreadLocal 用的就是开放定址法中的线性探测。
所谓线性探测就是,如果某个位置的值已经存在了,那么就在原来的值上往后加一个单位,直至不发生哈希冲突,就像这样的:
上面的动图就是需要在一个长度为 7 的数组里面,再放一个进过 hash 计算后为下标为 2 的数据,但是该位置上有值,也就是发生了 hash 冲突。
于是解决 hash 冲突的方法就是一次次的往后移,直到找到没有冲突的位置。
所以,当我们取值的时候如果发生了 hash 冲突也需要往后查询,这就是上面标号为 ③ 的 while 循环代码的其中一个目的。
当然还有其他目的,就隐藏在 440 行的 expungeStaleEntry 方法里面。不是本文重点,就不多说了。
但是如果你不知道这个方法,你一定要去查阅一下相关的资料,有可能会在一定程度上改变你印象中的:用 ThreadLocal 会导致内存泄漏的风险。
至少,你可以知道 JDK 为了避免内存泄漏的问题,是做了自己的最大努力的。
好了,不扯远了,说回来。
从上面我们知道了,从 ThreadLocal 中获取 InternalThreadLocalMap 会经历如下步骤:
1.计算 hash 值。
2.判断通过 hash 值是否能直接获取到目标对象。
3.如果没有获取到目标对象则往后遍历,直至获取成功或者循环结束。
比从 InternalThread 里面获取 InternalThreadLocalMap 复杂多了。
现在你知道了 fastGet/slowGet 这个两个方法中的快慢,指的是从两个不同的 ThreadLocal 中获取 InternalThreadLocalMap 的操作的快慢。而快慢的根本原因是数据结构的差异。
好,现在我们获取到 InternalThreadLocalMap 了,接着看 set 方法: