赞
踩
ThreadLocal本身并不存储值,它只是作为一个key来让线程从ThreadLocalMap获取value。
ThreadLocal提供了线程的局部变量,每个线程都可以通过set()
和get()
来对这个局部变量进行操作,但不会和其他线程的局部变量进行冲突,实现了线程的数据隔离
=>往ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的。
ThreadLocalMap没有链表结构,冲突时会线性向后查找。
这里还画了一个Entry
中的key
为null
的数据(Entry=2 的灰色块数据),因为key
值是弱引用类型,所以会有这种数据存在。在set
过程中,如果遇到了key
过期的Entry
数据,实际上是会进行一轮探测式清理操作的,具体操作方式后面会讲到。
ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用。
想要避免内存泄露就要手动remove()掉!
原因是如果不使用弱引用,那么当持有value的强引用释放掉后,当线程没有回收释放时,threadLocalMap会一直持有ThreadLocal以及value的强应用,导致value不能够被回收,从而造成内存泄漏。
通过使用弱引用,当ThreadLocal的强引用释放掉后,通过一次系统gc检查,发现ThreadLocal对象只有threadLocalMap中Entry的若引用持有,此时根据弱引用的机制就会回收ThreadLocal对象,从而避免了内存泄露。
ThreadLocal中有一个属性为HASH_INCREMENT = 0x61c88647
每当创建一个ThreadLocal
对象,这个ThreadLocal.nextHashCode
这个值就会增长 0x61c88647
。
这个值很特殊,它是斐波那契数 也叫 黄金分割数。hash
增量为 这个数字,带来的好处就是 hash
分布非常均匀。
接着看剩下for
循环中的逻辑:
key
值对应的桶中Entry
数据为空,这说明散列数组这里没有数据冲突,跳出for
循环,直接set
数据到对应的桶中key
值对应的桶中Entry
数据不为空k = key
,说明当前set
操作是一个替换操作,做替换逻辑,直接返回key = null
,说明当前桶位置的Entry
是过期数据,执行replaceStaleEntry()
方法(核心方法),然后返回for
循环执行完毕,继续往下执行说明向后迭代的过程中遇到了entry
为null
的情况Entry
为null
的桶中创建一个新的Entry
对象++size
操作cleanSomeSlots()
做一次启发式清理工作,清理散列数组中Entry
的key
过期的数据size
超过了阈值(数组长度的 2/3),进行rehash()
操作rehash()
中会先进行一轮探测式清理,清理过期key
,清理完成后如果size >= threshold - threshold / 4,就会执行真正的扩容逻辑(扩容逻辑往后看)ThreadLocalMap
的两种过期key
数据清理方式:探测式清理和启发式清理。
我们先讲下探测式清理,也就是expungeStaleEntry
方法,遍历散列数组,从开始位置向后探测清理过期数据,将过期数据的Entry
设置为null
,沿途中碰到未过期的数据则将此数据rehash
后重新在table
数组中定位,如果定位的位置已经有了数据,则会将未过期的数据放到最靠近此位置的Entry=null
的桶中,使rehash
后的Entry
数据距离正确的桶的位置更近一些。
启发式清理被作者定义为:Heuristically scan some cells looking for stale entries.
InheritableThreadLocal
在异步场景下给子线程共享父线程中创建的线程副本数据的。
实现原理是子线程是通过在父线程中通过调用new Thread()
方法来创建子线程,Thread#init
方法在Thread
的构造方法中被调用。在init
方法中拷贝父线程数据到子线程中。
InheritableThreadLocal
仍然有缺陷,一般我们做异步化处理都是使用的线程池,而InheritableThreadLocal
是在new Thread
中的init()
方法给赋值的,而线程池是线程复用的逻辑,所以这里会存在问题。
(阿里巴巴开源了一个TransmittableThreadLocal
组件就可以解决这个问题)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。