ThreadLocal笔记

Scroll Down

ThreadLocal结构

每个Thread对象持有一个ThreadLocalMap,key为ThreadLocal,value为具体的值。用户操作ThreadLocal对象实际上是操作当前线程的ThreadLocalMap,向里面放入数据。

ThreadLocalMap set流程

1、根据传入的 key 计算数组下标位置
2、根据计算得到的下标进行线性探测,寻找空的位置以存放元素
----2.1、检测到存在相同 key 的元素,进行值覆盖,然后结束流程即可
----2.2、检测到无效的元素,则重用无效元素位置,并尝试清理无效的元素
--------2.2.1、检测无效元素时会向后遍历数组如果发现key相同的元素那么会执行一次位置交换。(性能原因),并且执行一次线性清理。线性清理就是发现key为空的就直接清理,不为空的就rehash。
3、在某个空位置存放数据
4、如果不是值覆盖或重用无效元素位置的情况,那么需要判断是否需要 rehash

ThreadLocal数组移动的原因

在清理无效数据或者遇到相同的entry的key的情况下:遇到的entry是有效的,但是不是在自己原本的位置上,而是被hash冲突所迫而在其他位置上的,则把他们搬去离原本位置最近的后边空槽上。这样在get的时候,会最快找到这个entry,减少开放定址法遍历数组的时间。

内存泄漏

get,set两个方法都不能完全防止内存泄漏,还是每次用完ThreadLocal都remove一下
ThreadLocal中弱引用
强引用是我们在编程过程中使用的最简单的引用,如代码String s=”abc”中变量s就是字符串对象”abc”的一个强引用。任何被强引用指向的对象都不能被垃圾回收器回收,这些对象都是在程序中需要的。弱引用使用java.lang.ref.WeakReference class 类来表示,你可以使用如下代码创建弱引用:
代码如下:

Counter counter = new Counter(); // strong reference - line 1
WeakReferenceCounter weakCounter = new WeakReferenceCounter(counter);
//weak reference
counter = null; // now Counter object is eligible for garbage collection

现在只要你给强引用对象counter赋空值null,该对象就可以被垃圾回收器回收。因为该对象此时不再含有其他强引用,即使指向该对象的弱引用weakCounter也无法阻止垃圾回收器对该对象的回收。相反的,如果该对象含有软引用,Counter对象不会立即被回收,除非JVM需要内存。Java中的软引用使用java.lang.ref.SoftReference类来表示。
相关内存泄漏的文章:
https://www.cnblogs.com/lqlqlq/p/13302901.html

为什么每次清理和移动都是以空位置来做边界的?

其实正常情况下,ThreadLocalMap的数组中的数据应该不会存在空位置的,即使存在也会在set、get、rehash操作后都会优先使用空位置的。

为什么ThreadLocalMap使用开放定址法解决哈希冲突

在开放寻址法中,所有的数据都存储在一个数组中,比起链表法来说,冲突的代价更高。
所以,使用开放寻址法解决冲突的散列表,装载因子的上限不能太大。这也导致这种方法比链表法更浪费内存空间。
但是反过来看,链表法指针需要额外的空间,故当结点规模较小时,开放寻址法较为节省空间,而若将节省的指针空间用来扩大散列表的规模,可使装填因子变小,这又减少了开放寻址法中的冲突,从而提高平均查找速度。
所以这正是threadlocalmap选择开放寻址法的原因。

开放定址法存储数据的两个缺点

1、开放定址发存储数据是根据key的哈希值计算出一个数组的下标A,每个计算出相同的下标A的元素存放的时候都是从头到尾优先找到不为空的元素然后放进去。有个特殊情况就是比如一个元素计算出来的下标是11,然后这时又有一个元素计算的下标也是11,那么这时候只能放到12上面,这时有个元素根据hash值计算出来的下标是12那么这时候需要把元素放到13上面,那么这时就出现了hash计算出来的key11和key12元素交替的情况,这时也是可以继续运行的,通过元素的下一个节点一直去判断。但是这种情况比较耗费性能,因为在查找期间有不属于这个key的元素也在里面,查找效率不高。这是开放定址法的一个缺点。

2、另外一个缺点是,会造成空间的碎片化。比如key1和key1001的情况,只要后续没有key计算1-1001的情况,那么这段空间永远无法被使用到。