说明
面试官:讲讲你对 ThreadLocal 的一些理解。
那么我们该怎么回答呢????你也可以思考下,下面看看零度的思考;
- ThreadLocal 用在什么地方?
- ThreadLocal 一些细节!
- ThreadLocal 的最佳实践!
- 思考
ThreadLocal 用在什么地方?
讨论 ThreadLocal 用在什么地方前,我们先明确下,如果仅仅就一个线程,那么都不用谈 ThreadLocal 的,ThreadLocal 是用在多线程的场景的!!!
ThreadLocal 归纳下来就2类用途:
- 保存线程上下文信息,在任意需要的地方可以获取!!!
- 线程安全的,避免某些情况需要考虑线程安全必须同步带来的性能损失!!!
保存线程上下文信息,在任意需要的地方可以获取!!!
由于 ThreadLocal 的特性,同一线程在某地方进行设置,在随后的任意地方都可以获取到。从而可以用来保存线程上下文信息。
常用的比如每个请求怎么把一串后续关联起来,就可以用 ThreadLocal 进行 set,在后续的任意需要记录日志的方法里面进行get获取到请求id,从而把整个请求串起来。
还有比如 Spring 的事务管理,用 ThreadLocal 存储 Connection,从而各个 DAO 可以获取同一 Connection,可以进行事务回滚,提交等操作。
线程安全的,避免某些情况需要考虑线程安全必须同步带来的性能损失!!!
ThreadLocal 为解决多线程程序的并发问题提供了一种新的思路。每个线程往 ThreadLocal 中读写数据是线程隔离,互相之间不会影响的。
由于不需要共享信息,自然就不存在竞争问题了,从而保证了某些情况下线程的安全,以及避免了某些情况需要考虑线程安全必须同步带来的性能损失!!!
ThreadLocal一些细节!
ThreaLocal使用示例代码:
public class ThreadLocalTest {
private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
new Thread(() -> {
try {
for (int i = 0; i < 100; i++) {
threadLocal.set(i);
System.out.println(Thread.currentThread().getName() + "====" + threadLocal.get());
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} finally {
threadLocal.remove();
}
}, "threadLocal1").start();
new Thread(() -> {
try {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "====" + threadLocal.get());
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} finally {
threadLocal.remove();
}
}, "threadLocal2").start();
}
}
代码运行结果:
threadLocal1====0
threadLocal2====null
threadLocal2====null
threadLocal1====1
threadLocal2====null
threadLocal1====2
threadLocal2====null
......
从运行的结果我们可以看到 threadLocal1 进行 set 值对 threadLocal2 并没有任何影响!
Thread、ThreadLocalMap、ThreadLocal 总览图:
Thread 类有属性变量 threadLocals(类型是ThreadLocal.ThreadLocalMap),也就是说每个线程有一个自己的ThreadLocalMap ,所以每个线程往这个 ThreadLocal 中读写隔离的,并且是互相不会影响的。
一个ThreadLocal只能存储一个Object对象,如果需要存储多个Object对象那么就需要多个ThreadLocal!!!
如图:
看到上面的几个图,大概思路应该都清晰了,我们 Entry 的 key 指向 ThreadLocal 用虚线表示弱引用 ,下面我们来看看 ThreadLocalMap:
Java 对象的引用包括 : 强引用,软引用,弱引用,虚引用 。
因为这里涉及到弱引用,简单说明下:
弱引用也是用来描述非必需对象的,当 JVM 进行垃圾回收时,无论内存是否充足,该对象仅仅被弱引用关联,那么就会被回收。
当仅仅只有 ThreadLocalMap 中的 Entry 的 key 指向 ThreadLocal 的时候,ThreadLocal 会进行回收的!!!
ThreadLocal 被垃圾回收后,在 ThreadLocalMap 里对应的 Entry 的键值会变成 null,但是 Entry 是强引用,那么Entry 里面存储的 Object,并没有办法进行回收,所以 ThreadLocalMap 做了一些额外的回收工作。
虽然做了但是也会存在内存泄漏风险(我没有遇到过,网上很多类似场景,所以会提到后面的 ThreadLocal 最佳实践!!!)
ThreadLocal 的最佳实践!
备注:很多时候,我们都是用在线程池的场景,程序不停止,线程基本不会销毁!!!
由于线程的生命周期很长,如果我们往 ThreadLocal 里面 set 了很大很大的 Object 对象,虽然 set、get 等等方法在特定的条件会调用进行额外的清理,但是 ThreadLocal 被垃圾回收后,在 ThreadLocalMap 里对应的 Entry 的键值会变成 null,但是后续在也没有操作 set、get 等方法了。
鉴于上面提到的内存泄露的风险,所以最佳实践,应该在我们不使用的时候,主动调用 remove 方法进行清理。
这里把 ThreadLocal 定义为 static 还有一个好处就是,由于 ThreadLocal 有强引用在,那么在 ThreadLocalMap 里对应的 Entry 的键会永远存在,那么执行 remove 的时候就可以正确进行定位到并且删除!!!
最佳实践做法应该为:
try {
// 其它业务逻辑
} finally {
threadLocal对象.remove();
}
思考
如果面试的时候,可以把上面的内容都可以讲到,个人觉得就非常好了,回答的就挺完美了。但是如果你可以进行下面的回答,那么就更完美了。
对于 ThreadLocal,我在看 Netty 源码的时候,还了解过 FastThreadLocal,xxxxx 一些列内容,那就是一个升级了。
作者:殷建卫 链接:https://www.yuque.com/yinjianwei/vyrvkf/ipdwh4 来源:殷建卫 - 架构笔记 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。