一、概述
ThreadLocal是一个实现了在不同线程保持一个变量副本的功能,使得在多线程的情况下,各线程保持的副本不受影响,ThreadLocal的原理很简单,就是在每个线程Thread中维护有一个map变量,我们为副本创建的ThreadLocal作为key,值当做value保存在这个map中,以此来实现线程间副本的独立
|
|
二、ThreadLocalMap
ThreadLocal提供了几个核心方法来实现值的存取:set、get、remove,这几个方法的实现都依托ThreadLocal中实现的静态内部类ThreadLocalMap;ThreadLocalMap是一个实现map存取功能的包装类,其中Entry类型数组table存储了线程中需要保存副本的数据,初始化长度16,超过3/4时则自动扩容为原来的2倍,Entry使用魔数0x61c88647来实现数组角标的散列
每个线程Thread都持有一个ThreadLocalMap实例threadLocals, 因此它们是不存在并发竞争的。可以理解为每个线程有自己的变量副本;
Entry数组角标的散列算法
我们可以观察到,在ThreadLocalMap内部都是使用的一个&操作来生成数组的下标,
|
|
threadLocalHashCode是ThreadLocalMap中的一个常量,在ThreadLocal每次被实例时,都会累加0x61c88647,这个0x61c88647数值我们一般称作魔数,非常强大
得到的结果按位和 ThreadLocalMap 的容量-1 做 & 操作。(这里实际上对应了哈希函数的一般操作步骤的第二步,也就是将第一步计算的值和哈希表的容量做取模操作。另外,这里ThreadLocalMap 的容量必须为2的幂,之所以有这样的规定,是因为,当求解 x % y 时,如果y = 2^n 那么取模操作可以转换为按位与操作 x & (y - 1),效率会比取模操作高,取模操作可以看作是除操作,而除操作是性能比较低的),其它的一些hash算法在得到hash值的情况下都是对数组lenth-1取模
|
|
核心原理
|
|
ThreadLocalMap内部实现介绍
内部的Entry数组继承自WeakReference,ThreadLocal类型的key的弱引用防止内存泄漏,如上面说述,内部通过ThreadLocal的常量和数组lenth-1的hash散列来生成数组下标,进行值的存取。
set
12345678910111213141516171819202122232425private void set(ThreadLocal<?> key, Object value) {Entry[] tab = table;int len = tab.length;int i = key.threadLocalHashCode & (len-1);//获得散列值for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {ThreadLocal<?> k = e.get();if (k == key) {//key存在则替换valuee.value = value;return;}if (k == null) {//k==null,则覆盖并清除等于key=null时的陈旧数据(key=null时,是因为弱引用被回收)replaceStaleEntry(key, value, i);//将null值删除后,数组需要重新散列排列return;}}tab[i] = new Entry(key, value);//没有存过,则直接存入int sz = ++size;if (!cleanSomeSlots(i, sz) && sz >= threshold)//数组长度超过限制,扩容重新排列rehash();}rehash
|
|
- resize
|
|
- remove
|
|
注意事项
如果在项目中有使用线程池,则需要在任务执行完后及时清除ThreadLocal,防止产生不符合预期的结果