ThreadLocal
是什么?
线程本地变量,可以实现线程之间的隔离,线程内部共享。我们平时都是在类中作为私有静态变量来使用的,目的是为了让一些状态(用户ID,事务ID)与线程进行关联。
有什么用
实现每个线程内都有一份专属的本地变量副本。
实现线程之间隔离,线程内共享
共享这个更重要,有人就要问,线程内共享,难道直接传参就好了吗,但你想想这样不丑陋吗,如果你的方法调用链很长的花,例如Spring的事务机制,我们都知道Spring事务支持嵌套,如果你的事务嵌套10层,那你不是每个方法都要加一个 Connection 参数?那不丑陋死了
API
| 方法 | 作用 |
|---|---|
| initialValue() | 定义初始值,默认null,可以重写 |
| get() | 拿当前线程自己的值,没有就用initialValue() |
| set(value) | 给当前线程自己设置一个新值 |
| remove() | 删除当前线程自己的值,防内存泄漏 |
原理
ThreadLocal,Thread和ThreadLocalMap的关系

当我们在往ThreadLocal里set值时,其实是往Thread的ThreadLocalMap里set值,key为ThreadLocal实例(弱引用),而value为我们set的值。为什么这就是线程安全的了?原因是ThreadLocalMap是Thread里维护的一个变量,可以说是线程私有的。
ThreadLocal可以认为是 ThreadLocalMap的封装,传递了变量值,而且它本身并不存储值,而是把自己作为一个key,来让线程从ThreadLocalMap获取得到值

1 | class ServiceA{ |
ThreadLocal内存泄漏问题
内存泄漏是什么? :
不再使用的内存不能被回收
为什么Entry的key要为弱引用,不用如何?

主要是为了能让ThreadLocal对象能够回收掉。若这个key为强引用,那么会导致key指向的ThreadLocal对象,以及value对象无法被GC。若为弱引用,key所指向的ThreadLocal就可以被回收且Entry指向的对象为null,减少内存泄漏概率
但这单单不够,value为强引用,且引用链依然存在,仍然无法被GC。
其实ThreadLocal内存泄漏的说法只存在于线程池的情况,比如tomcat线程池,它会复用线程,导致你的线程一直存活。没有线程复用的化,线程执行后立马销毁了,那其实啥事都没有了
如何做?
手动remove,但其实但key为null后,我们再次去get,set也会自动清空

最后重中之重!!!
是否会内存泄露不是取决于是否remove,而是取决于怎么用。平时使用我们都是将ThreadLocal声明为一个
static对象,那这样的话它的生命周期就会和容器保持一样,那么它就不可能被回收掉,也就是这时候key是
个强引用,我们说的内存泄露主要是说我们无法把value置为null了,而这种情况下是不会出现的,因为key不
为null,我们还是可以通过key拿到我们的value的。那不正常情况,那就是一些傻子会把ThreadLocal当做局
部变量使用,这种情况就会出现随着线程结束,出现key也就是ThreadLocal被回收,因为它是局部变量么。
那么也就是key没了,但value还在,而这个kv(Entry)是存于ThreadLocalMap里的,而ThreadLocalMap是
和线程强相关的,而在tomcat线程里,有些核心线程是一直存在,ThreadLocalMap无法被回收掉。也就导
致了内存泄露
ThreadLocalMap如何解决哈希冲突?
开放寻址法(当发生Hash冲突时,则加1向后寻址,直到找到空位置或者被垃圾回收的位置),不同于HashMap采用的拉链法,也就是说ThreadLocalMap没有采用链表结构存储。而且在此期间还会清理掉key为null的Entry
Hash表的默认大小为 10,扩容阈值为 len*2/3 也就是10,大于10就扩容,扩容两倍
InheritableThreadLocal
可以解决父子线程间ThreadLocal无法共享的问题
它的原理是 子线程是在父线程中通过new Thread()创建的,在Thread构造方法中调用init,在init方法中父线程的数据会传递到子线程

但我们平常开发中异步都是配合线程池来使用的,这种问题还是无法解决

TransmitThreadLocal?
在ThreadLocal基础上进行了增强,增加了值拷贝和传递的功能
不同于 InheritableThreadLocal, TTL管它什么新线程/线程池,它自己控制拷贝和恢复
新一代ThreadLocal?ScopedValue
这东西鉴于ThreadLocal的缺点