【Java】ThreadLocal 简介
前言
在面试环节中,考察”ThreadLocal”也是面试官的家常便饭,所以对它理解透彻,是非常有必要的.
有些面试官会开门见山的提问:
- “知道ThreadLocal吗?”
- “讲讲你对ThreadLocal的理解”
当然了,也有面试官会慢慢引导到这个话题上,比如提问“在多线程环境下,如何防止自己的变量被其它线程篡改”,将主动权交给你自己,剩下的靠自己发挥。
ThreadLocal是什么
首先,它是一个数据结构,有点像HashMap,可以保存”key : value”键值对,但是一个ThreadLocal只能保存一个,并且各个线程的数据互不干扰。
看看set(T value)
和get()
方法的源码:
public void set(T value) { |
可以发现,每个线程中都有一个ThreadLocalMap
数据结构,当执行set方法时,其值是保存在当前线程的threadLocals
变量中,当执行set方法中,是从当前线程的threadLocals
变量获取。
所以在线程1中set的值,对线程2来说是摸不到的,而且在线程2中重新set的话,也不会影响到线程1中的值,保证了线程之间不会相互干扰。
那每个线程中的ThreadLocalMap
究竟是什么?
ThreadLocalMap
Thread
类有一个类型为ThreadLocal.ThreadLocalMap
的实例变量threadLocals
,也就是说每个线程有一个自己的ThreadLocalMap
。
ThreadLocalMap
有自己的独立实现,可以简单地将它的key
视作ThreadLocal
,value
为代码中放入的值(实际上key
并不是ThreadLocal
本身,而是它的一个弱引用)。
每个线程在往ThreadLocal
里放值的时候,都会往自己的ThreadLocalMap
里存,读也是以ThreadLocal
作为引用,在自己的map
里找对应的key
,从而实现了线程隔离。
ThreadLocalMap
有点类似HashMap
的结构,只是HashMap
是由数组+链表实现的,而ThreadLocalMap
中并没有链表结构。
我们还要注意Entry
, 它的key
是ThreadLocal<?> k
,继承自WeakReference
, 也就是我们常说的弱引用类型。
ThreadLocalMap 的 Hash 算法
既然是Map
结构,那么ThreadLocalMap
当然也要实现自己的hash
算法来解决散列表数组冲突问题。
int i = key.threadLocalHashCode & (len-1); |
ThreadLocalMap
中hash
算法很简单,这里i
就是当前 key 在散列表中对应的数组下标位置。
这里最关键的就是threadLocalHashCode
值的计算,ThreadLocal
中有一个属性为HASH_INCREMENT = 0x61c88647
public class ThreadLocal<T> { |
每当创建一个ThreadLocal
对象,这个ThreadLocal.nextHashCode
这个值就会增长 0x61c88647
。
这个值很特殊,它是斐波那契数 也叫 黄金分割数。hash
增量为 这个数字,带来的好处就是 hash
分布非常均匀。
ThreadLocalMap 的 Hash 冲突
虽然ThreadLocalMap
中使用了黄金分割数来作为hash
计算因子,大大减少了Hash
冲突的概率,但是仍然会存在冲突。
HashMap
中解决冲突的方法是在数组上构造一个链表结构,冲突的数据挂载到链表上,如果链表长度超过一定数量则会转化成红黑树。
而 ThreadLocalMap
中并没有链表结构,所以这里不能使用 HashMap
解决冲突的方式了。
如上图所示,如果我们插入一个value=27
的数据,通过 hash
计算后应该落入槽位 4 中,而槽位 4 已经有了 Entry
数据。
此时就会线性向后查找,一直找到 Entry
为 null
的槽位才会停止查找,将当前元素放入此槽位中。当然迭代过程中还有其他的情况,比如遇到了 Entry
不为 null
且 key
值相等的情况,还有 Entry
中的 key
值为 null
的情况等等都会有不同的处理,后面会一一详细讲解。
这里还画了一个Entry
中的key
为null
的数据(Entry=2 的灰色块数据),因为key
值是弱引用类型,所以会有这种数据存在。在set
过程中,如果遇到了key
过期的Entry
数据,实际上是会进行一轮探测式清理操作的,具体操作方式后面会讲到。
内存泄漏
ThreadLocal可能导致内存泄漏,为什么?
(GC相关内容先跳过,以后再补充!)
如何避免内存泄漏
待补充
ThreadLocalMap.set() 详解
待补充
ThreadLocalMap.get()详解
待补充