常见问题
一、ThreadLocal是什么?为什么要使用ThreadLocal
ThreadLocal——线程本地变量。一种用空间换时间的方式,把上锁替换成本地变量。
我们创建一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个本地拷贝,多个线程操作这个变量的时候,实际是在操作自己本地内存里面的变量,从而起到线程隔离的作用,避免了并发场景下的线程安全问题。
二、一个ThreadLocal的使用案例
@Test
public void testTreadLocalSetAndGet(){
// 提供一个ThreadLocal对象
ThreadLocal tl = new ThreadLocal();
// 开启两个线程
new Thread(()->{
tl.set("萧焱");
System.out.println(Thread.currentThread().getName()+":"+tl.get());
System.out.println(Thread.currentThread().getName()+":"+tl.get());
System.out.println(Thread.currentThread().getName()+":"+tl.get());
},"蓝色").start();
new Thread(()->{
tl.set("药尘");
System.out.println(Thread.currentThread().getName()+":"+tl.get());
System.out.println(Thread.currentThread().getName()+":"+tl.get());
System.out.println(Thread.currentThread().getName()+":"+tl.get());
},"绿色").start();
}
三、ThreadLocal的原理
- 每个线程都有一个 属于自己的ThreadLocalMap(ThreadLocal内部类),Map 中元素的键为 ThreadLocal,而值对应线程的变量副本。Map 是数组实现,使用线性探测解决hash冲突,需要手动调用set、get、remove防止内存泄漏。
- 一个线程内可以存在多个 ThreadLocal 对象。所以其实是 ThreadLocal 内部维护了一个 Map ,这个 Map 不是直接使用的 HashMap ,而是 ThreadLocal 实现的一个叫做 ThreadLocalMap 的静态内部类。
- 每个线程有唯一的一个threadLocalMap。ThreadLocalMap内部维护着Entry数组,每个Entry代表一个完整的对象,ThreadLocal对象存储为key,”线程局部变量”为value,所以一个线程下可以保存多个”线程局部变量”。
- ThreadLocal适合作为线程上下文变量,简化线程内传参,实现线程隔离。
四、为什么会导致内存泄漏呢?是因为弱引用吗?
- 什么是内存泄漏?
在ThreadLocal中内存泄漏是指ThreadLocalMap中的Entry中的key为null,而value不为null。因为key为null导致value一直访问不到,而根据可达性分析导致在垃圾回收的时候进行可达性分析的时候,value可达从而不会被回收掉,但是该value永远不能被访问到,这样就存在了内存泄漏。 - 怎么解决?
对应的 value 在下一次 ThreadLocalMap 调用 set、get、remove 方法时被清除,但是这几个方法需要被显式调用。
五、Key为什么要设计成弱引用呢?强引用不行?
key如果是强引用会导致线程不被回收,key对应ThreadLocal也不被回收,所以要改为弱引用。至于value一定是强引用,所以必须用完调用remove方法。
六、ThreadLocal的应用场景和使用注意点
ThreadLocal的很重要一个注意点,就是使用完,要手动调用remove()
而ThreadLocal的应用场景主要有以下这几种:
- 使用日期工具类,当用到SimpleDateFormat,使用ThreadLocal保证线性安全
- 全局存储用户信息(用户信息存入ThreadLocal,那么当前线程在任何地方需要时,都可以使用)√
- 保证同一个线程,获取的数据库连接Connection是同一个,使用ThreadLocal来解决线程安全的问题
- 使用MDC保存日志信息。