一、ThreadLocal 引用关系
图解关系说明:
- 每个线程拥有自己的
ThreadLocalMap
属性; - ThreadLocalMap 的存储结构为
Entry[]
数组; - Entry的Key是ThreadLocal类型且
弱引用
指向ThreadLocal对象,Value是我们自己定义的泛型值对象; - ThreadLocal对象的生命周期是GC,引用则是方法结束,ThreadLocalMap的生命周期是和Thread同步;
解析:当线程使用Threadlocal 时,是将Threadlocal 当做自己线程属性ThreadLocalMap中一个Entry的key值,实际上存放的变量是Entry的value值,实际要使用的值是value值。
- value值为什么不存在并发问题呢,因为它只有一个线程能访问。
- threadlocal可以当做一个索引,可以有多个threadlocal 变量,不同的threadlocal对应于不同的value值,他们之间互不影响。
- ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。
强引用:GC不会被清理掉;
软引用:SoftReference内存不足时会背垃圾回收器回收;
弱引用:WeakReference 当jvm进行垃圾回收就回收;
虚引用:没有引用
二、ThreadLocal 使用方法解析
// TODO:对应源码及注释后期补上
- get——获取threadlocal局部变量
// ThreadLocal的get方法:先拿到当前线程的TLMap,再根据当前的TL对象找到存储的值
public T get() {
Thread t = Thread.currentThread();// 获取当前线程
// 每个线程都有一个自己的 ThreadLocalMap, TLM里保存的是所有的 ThreadLocal 变量
ThreadLocalMap map = getMap(t);
if (map != null) {
// ThreadLocalMap 的key就是当前ThreadLocal的对象实例; 多个ThreadLocal变量都是存放再这个Map中
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
T result = (T)e.value;// map中取出来的就是我们的ThreadLocal变量
return result;
}
}
// 如果为空则初始化一下
return setInitialValue();
}
- set——设置threadlocal局部变量
- initialvalue——设置局部变量的初始值
- remove——删除该局部变量
三、ThreadLocal 常见面试解答
1. ThreadLocal和普通线程中的变量的区别?
- 普通变量在多线程环境下是线程共享的,会有并发问题;
- ThreadLocal在多线程环境下为线程私有变量,值对其他线程不可见,不会有多线程访问不安全的情况。
2. key为什么设置为弱引用?
- 强引用也无能为力:业务使用完ThreadLocal的时候,ThreadLocalRef被回收了,但是还是存在key的强引用,导致Entry对象还是无法被回收;
- 弱引用的好处:当前线程仍然运行的情况下,就算忘记调用Remove()方法,在下次使用ThreadLocal的时候,会对 key为null的Value进行清除操作,比强引用多了一层保障。
3. 为什么不使用当前线程作为当前的key?
-
直接用当前线程来作为ThreadLocalMap的key,无法区分放入ThreadLocalMap中的多个value。
比如放入了两个字符串,无法判断取出来的是哪一个字符串! -
使用ThreadLocal作为key就不一样了:每一个ThreadLocal对象都可以由ThreadLocalHashCode属性唯一区分或者说每一个ThreadLocal对象都可以由这个对象的名字唯一区分,所以可以用不同的ThreadLocal作为key,区分不同的value,方便存取。
4. 如何避免内存泄漏?
- 将ThreadLocal定义为 private static ,使生命周期更长,保证ThreadLocal的强引用一直存在而不会回收,保证在ThreadLocal的弱引用能够找到Entry的值,并remove掉(ps:这段是其他文章中抄的,我认为应该理解成:下次操作ThreadLocalMap的时候 JDK可以通过remove掉key为null的 Entry。[这个欢迎有大佬指正])
- 每次使用完ThreadLocal的时候都调用remove()方法。
5. ThreadLocal有哪些使用场景?
ThreadLocal使用于以下两种场景:
- 每个线程需要有自己单独的实例
- 实例需要在多个方法中共享,但不希望其他线程共享
- JavaWeb中使用ThreadLocal传递Session信息;
- 数据库连接,处理数据库事务;
- 日志、调用链路追踪;