[Java-juc] ThreadLocal
2026年3月22日大约 3 分钟
[Java-juc] ThreadLocal
ThreadLocal 是什么?
ThreadLocal 是一种用于实现线程局部变量的工具类。它允许每个线程都拥有自己的独立副本,从而实现线程隔离。
ThreadLocal 有哪些优点?
- 避免了共享变量引起的线程安全问题;
- 变量不需要同步处理,因此能够避免资源竞争。
你在工作中用到过 ThreadLocal 吗?
- 用过,用来存储用户信息。在拦截器中就存入 用户信息进入 ThreadLocal。
ThreadLocal 怎么实现的呢?
ThreadLocal 的实现原理是,每个线程维护一个 Map,key 为 ThreadLocal 对象,value 为想要实现线程隔离的对象。
- 通过 ThreadLocal 的 set 方法将对象存入 Map 中。
- 通过 ThreadLocal 的 get 方法从 Map 中获取对象。
- Map 的大小由 ThreadLocal 对象的多少决定。
什么是弱引用,什么是强引用?
注意
ThreadLocal 底层 ThreadLocalMap 中的 Key 是弱引用(WeakReference),而 Value 是强引用。
两者最大的区别在于,垃圾回收器(GC)是否会回收该对象。
- 强引用:只要强引用存在,垃圾回收器就不会回收该对象。即使内存不足,GC 也不会回收强引用对象。
- 比如:
Object obj = new Object();这里的 obj 就是一个强引用。
- 比如:
- 弱引用:当一个对象只有弱引用时,在下一次垃圾回收发生时,无论内存是否充足,该对象都会被回收。
- 比如:
WeakReference<Object> weakRef = new WeakReference<>(new Object());这里的 weakRef 就是一个弱引用。
- 比如:
ThreadLocal 内存泄露是怎么回事?
由于 ThreadLocal 底层 ThreadLocalMap 的 Key 是 弱引用,但 Value 是强引用。
- 如果一个线程一直在运行,并且 value 一直指向某个强引用对象,那么这个对象就不会被回收,从而导致内存泄漏。
那怎么解决内存泄漏问题呢?
- 用完
ThreadLocal后,及时调用 remove() 方法释放内存空间。
你每次操作都会remove吗?
- 不会,我的使用原则是:
- 在方法级别使用时,使用,try-finally,在 finally 块中调用
remove()方法。 - 请求级别使用时,通过拦截器或Filter统一清理。
- 如果存储的对象较大,我会使用完立即
remove()
那为什么 key 要设计成弱引用?
弱引用好处在于,当内存不足时,垃圾回收器会自动回收弱引用的对象。
- 当 key 被回收,ThreadLocalMap 在进行 set、get 的时候就会对 key 为 null 的 Entry 进行清理。
你了解哪些 ThreadLocal 的改进方案?
ThreadLocalMap 的源码看过吗?
看过
- ThreadLocalMap 并没有 实现Map接口,是一个线性探测哈希表。
::: tips 提示 - 线性探测哈希表:当发生哈希冲突时,继续向后探测,直到找到一个空位。
:::
ThreadLocalMap 怎么解决 Hash 冲突的?
使用了开放定址法中的线程探测,解决哈希冲突。
为什么要用线性探测法而不是HashMap 的拉链法来解决哈希冲突?
因为ThreadLocalMap一般不会有太多key,线性探测更节省空间。
开放地址法了解吗?
是一种解决哈希冲突的方法,当发生哈希冲突时,继续向后探测,直到找到一个空位。
ThreadLocalMap 扩容机制了解吗?
ThreadLocalMap 采用先清理后扩容的策略,扩容时,数组长度翻倍,重新计算索引,发生哈希冲突时,采用线性探测法解决。
- ThreadLocalMap 达到阈值时不会立即扩容,会清理掉被垃圾回收的 Key ,然后在达到容量四方之三时扩容。
父线程能用 ThreadLocal 给子线程传值吗?
不能,父线程和子线程的 ThreadLocalMap 是独立的,父线程设置的值在子线程中不可见。
- 可以使用
InheritableThreadLocal来实现父线程给子线程传值的功能。
InheritableThreadLocal的原理了解吗?
在Thread中,定义了两个ThreadLocalMap
- 在
new Thread()时,会调用init()方法,检查父线程是否有InheritableThreadLocal,如果有,则拷贝父线程的InheritableThreadLocal到子线程中。