简介
ThreadLocal相当于线程私有属性(threadLocals)的封装,提供了#get、#set的API。
多线程情况下不会有并发的问题,通常用来做数据传输。如拦截token存储其用户信息,traceId、租户拦截等。
核心点
执行原理
一个线程,可以创建多个ThreadLocal,以每个ThreadLocal对象作为key,存储在当前线程的threadLocals中,类型是ThreadLocal.ThreadLocalMap。
#set方法解析
1. 先获取当前线程的ThreadLocalMap,对应字段为Thread.threadLocals
2. 如果线程的threadLocals为空,新建
3. threadLocals不为空,调用ThreadLocalMap#set
1. 获取到key的hashCode后计算出在Entry数组中对应的索引idx
2. 循环Entry数组,起始位置为计算出的索引idx
3. 如果key相等赋值value返回
4. 如果key为null,进行替换
5. 如果未找到,则idx+1,继续寻找。
6. 循环执行完后还未找到就在idx处,新建entry
TheadLocalMap与HashMap的区别
| 不同点 | ThreadLocalMap | HashMap |
|---|---|---|
| 引用 | key为弱引用 | key为强引用 |
| 结构 | 数组 | 数组 + 链表 |
| 解决hash冲突 | 开发地址法(向后+1保存元素) 因为数据量少,而且key又是弱引用,所以使用 | 链式地址法(将冲突单独保存到单向链表中) |
TheadLocalMap,Entry的key为什么要使用弱引用
防止内存泄漏。
弱引用的情况下:如果ThreadLocal为null,则可以将ThreadLocal进行回收,如果是强引用则不会进行回收。
jvm都有哪些引用
| 引用类型 | 特征 | 实现类 |
|---|---|---|
| 强引用 | 宁愿GC也不会回收 | |
| 软引用 | 内存不足时回首 | SoftReference |
| 弱饮用 | GC时回收 | WeakReference |
| 虚引用 | 任何时候都可能被GC回收。作用通知对象被回收,回调#finalize | PhantomReference |
对象引用与GC回收
将对象设为null,对象就会被回收吗?
不会被立即回收,将对象设为null,只是去掉其引用关系,内存对象还在,等GC过后才会回收。
GC判断对象回收有两种:
1. 引用计数。无法解决循环引用问题,没有被使用。
2. 引用可达性分析。从GCRoot开始,只要没有被引用到的都可以被回收。
GCRoot包括:
a. 方法执行时,虚拟机栈中引用的对象。
b. 静态属性属性引用的对象。
内存泄漏
有一些对象其实已经不再使用了,但是GC却回收不了,长期下去会导致内存溢出
为什么ThreadLocal执行完后需要remove
1. 因为ThreadLocalMap中Entry的value为强引用,如果一直不删除,会导致内存溢出。
2. 如果是使用线程池,防止因为线程复用,导致ThreadLocal被错用。
父子线程传值问题
InheritableThreadLocal:JVM自带
怎么解决的父子线程传值:
- Thread.inheritableThreadLocals,存储ThreadLocal.ThreadLocalMap
- InheritableThreadLocal,会设置Thread.inheritableThreadLocals字段,不会设置threadLocals
- 在创建子线程的时候,currentThread作为父线程, 父线程inheritThreadLocals不为空,则复制父类的inheritThreadLocals到子线程。
线程池的问题:
因为线程会复用,只会初始化一次,所以无法获取最新父类的threadLocals.
TransmittableThreadLocal:阿里的
怎么解决线程池复用问题: