标号为 ① 的地方就是往 InternalThreadLocalMap 这个数组中存放我们传进来的 value。
存的时候分为两种情况。
标号为 ② 的地方是数组容量还够,能放进去,那么可以直接设置。
标号为 ③ 的地方是数组容量不够用,需要扩容了。
这里抛出两个问题:
扩容是怎么扩的?
数组下标是怎么来的?
先看问题一,怎么扩容的?
看源码:
和 HashMap 里面的位运算异曲同工。
在 InternalThreadLocalMap 中扩容就是变成原来大小的 2 倍。从 32 到 64,从 64 到 128 这样。
扩容完成之后把原数组里面的值拷贝到新的数组里面去。
然后剩下的部分用 UNSET 填充。最后把我们传进来的 value 放到指定位置上。
再看看问题二,数组下标怎么来的?也就是这个 index:
从上往下看,可以看到最后,这个 index 本质上是一个 AtomicInteger 。
主要看一下标号为 ① 的地方。
index 每次都是加一,对应的是 InternalThreadLocalMap 里的数组下标。
第一眼看到的时候,里面的 if 判断 index<0 我是可以理解的,防止溢出嘛。
但是下面在抛出异常之前,还调用了 decrementAndGet 方法,又把值减回去了。
你说这是为什么?
开始我没想明白。但是有天晚上睡觉之前,电光火石一瞬间我想明白了。
如果不把值减回去,加一的代码还在不断的被调用,那么这个 index 理论上讲是有可能又被加到正数的,这一点你能明白吧?
为什么我说理论上呢?
int 的取值范围是 [-2147483648 到 2147483647]。
如果 int 从 0 增加,一直溢出到 -2147483648,再从 -2147483648 加到 0,中间有 4294967295 个数字。
一个数字对应数组的一个下标,就算里面放的是一个字节的 boolean 型,那么大概也就是 4T 的内存吧。
所以,我觉得这是理论上的。
到这一步,我们已经完成了从 Thread 里面取出 InternalThreadLocalMap ,并且往里面放数据的操作。
最后,InternalThreadLocal 的 set 方法只剩下最后一行代码,我们还没说:
就是 setIndexedVariable 方法返回 true 后,会执行 addToVariablesToRemove 方法。
这个方法其实就是在数组的第一个位置维护当前线程里面的所有的 InternalThreadLocalMap 。
这里的关键点其实就是这个变量:
static final,能保证 VARIABLE_TO_REMOVE_INDEX 恒等于 0,也就是数组的第一个位置。
用示例程序,给大家演示一下,它第一个位置放的东西:
在第 21 行打上断点,然后看一下执行完 addToVariablesToRemove 方法后,InternalThreadLocalMap 数组的情况:
诚不欺你,第 0 个位置上放的是所有的 InternalThreadLocal 的集合。
所以,我们看一下它的 size 方法,就能明白这里为什么要减一了:
那么在第一个位置维护线程里面所有的 InternalThreadLocal 集合的用处是什么?
看看它的 removeAll 方法: