源码略我千百遍,我待源码如初恋
1、为什么要引用ThreadLocal
线程副本,存放线程变量,在线程任务中进行跨方法变量传递,相比于锁以时间换空间的方式,ThreadLocal是采用以空间换时间
2、ThreadLocal源码解析
1、打开ThreadLocal源码,先看看整体结构,构造方法,核心方法set(t) , get(),remove(),核心内部类ThreadLocalMap
2、ThreadLcoal.set(value)方法,通过源码走读,可以清晰看懂ThreadLocalMap对象是Thread线程对象里面的一个属性,并且Map存的键值对是当前ThreadLocal对象,和传入的value值
3、ThreadLocalMap对象:在上面set方法中如果map为空,则初始化创建ThreadLocalMap对象,ThreadLcoalMap是ThreadLocal内部定义的类,内部定义了一个entry内部类,和hashMap的内部entry实现原理类似。内部有一个table数组,以及定义了一些相关属性,如初始长度,大小,临界长度等等
set源码整体实现比较简单,主要是弄清ThreadLocal存的key value对应的对象,以及ThreadLocalMap内部类的构造
4、ThreadLocal.get()方法:获取当前线程,通过线程获取对象ThreadLocalMap,通过getEntry(this)方法获取对象的value值,如果map为空,调用setInitialValue()方法返回一个null值。
5、remove方法 移除ThreadLocalMap缓存的ThreadLocal对象及对应的value,释放内存空间
ThreadLocal为何会内存泄露:
当把 threadlocal 变量置为 null 以后,没有任何强引用指向 threadlocal 实例,所以threadlocal 将会被 gc 回收。这样一来, ThreadLocalMap 中就会出现 key 为 null 的 Entry,就没有办法访问这些 key 为 null 的 Entry 的 value,如果当前 线程再迟迟不结束的话,这些 key 为 null 的Entry 的 value 就会一直存在一条强 引用链: Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value ,而这块 value 永 远不会被访问到了,所以存在着内存泄露。
只有当前 thread 结束以后, current thread 就不会存在栈中,强引用断开, Current Thread 、Mapvalue 将全部被 GC 回收。最好的做法是不在需要使用ThreadLocal 变量后,都调用它的 remove()方法,清除数据。
拓展:如何解决Hash冲突
开放定址法:
基本思想是,出现冲突后按照一定算法查找一个空位置存放,根据算法的不同又可以分为线性探测再散列、二次探测再散列、伪随机探测再散列。线性探测再散列即依次向后查找,二次探测再散列, 即依次向前后查找, 增 量为 1 、2 、3 的二次方,伪随机,顾名思义就是随机产生一个增量位移。
ThreadLocal 里用的则是线性探测再散列
链地址法:
这种方法的基本思想是将所有哈希地址为 i 的元素构成一个称为同义词链的单链表, 并将单链表的头指针存在哈希表的第 i 个单元中, 因而查找、插入和删 除主要在同义词链中进行。链地址法适用于经常进行插入和删除的情况
再哈希法:
这种方法是同时构造多个不同的哈希函数: Hi=RH1(key) i=1 ,2 ,… ,k 当哈希地址 Hi=RH1(key)发生冲突时,再计算 Hi=RH2(key)……,直到冲突 不再产生。这种方法不易产生聚集,但增加了计算时间。
建立公共溢出区
这种方法的基本思想是: 将哈希表分为基本表和溢出表两部分, 凡是和基本 表发生冲突的元素, 一律填入溢出表。