ThreadLocal面试总被问?我读完源码,反而期待面试官来考

张开发
2026/6/13 22:15:52 15 分钟阅读

分享文章

ThreadLocal面试总被问?我读完源码,反而期待面试官来考
ThreadLocal大家肯定都不陌生吧在工作中经常被用到面试当中基本也是必问的点。上次面试的时候就栽到ThreadLocal这里了本以为知道它的应用场景跟使用方法就够了谁知道面试官懂的太多了。我回去也是赶紧读了下ThreadLocal的源码今天和大家分享下。首先看下ThreadLocal的基本概念ThreadLocal是一个能够在并发环境下隔离用户线程的同步工具他通过为每个线程都建立一个本地的副本起到线程之间的共享变量互不影响主要的应用场景一般为存储上下文比如用户信息、日志记录、TraceID等。光看概念是不是挺懵啊跟着我看下面这三个问题看完你就不用在死记八股了。1.ThreadLocalMap是什么ThreadLocalMap是ThreadLocal类中的一个静态内部类并且也是Thread类中的一个属性重点要记住作用是跟线程做绑定这个ThreadLocalMap才是真正存储数据的地方接着往下看。2.ThreadLocalMap是如何做到线程隔离的看完下面这段代码你就明白了核心在 getMap(t) 方法上其实就是获取到当前线程中绑定的ThreadLocalMap每个线程实例独一份的存在所以启到线程隔离的作用。看代码注释ThreadLocal中的set()方法publicvoidset(Tvalue){// 1.先获取当前线程ThreadtThread.currentThread();// 2.获取当前线程中的ThreadLocalMap属性ThreadLocalMapmapgetMap(t);if(map!null){// 3.如果ThreadLocalMap不为空就将当前实例作为key要存储的数据作为value放到这个Map里。map.set(this,value);}else{// 4.否则就通过当前线程创建一个ThreadLocalMapcreateMap(t,value);}}ThreadLocal中的getMap()方法ThreadLocalMapgetMap(Threadt){// 拿到当前线程中的ThreadLocalMapreturnt.threadLocals;}ThreadLocal中的createMap()方法voidcreateMap(Threadt,TfirstValue){// 创建一个ThreadLocalMap赋值给当前线程// 用当前ThreadLocal的实例作为key用真实数据作为value。t.threadLocalsnewThreadLocalMap(this,firstValue);}通过上面三个方法就能确定不同的线程会创建属于自己的ThreadLocalMap并且通过ThreadLocal的实例this作为Map的key传入的真实数据作为Map的value所以我们的数据是因为放到了这个Map里并且这个Map又绑定到了线程上所以做到了数据隔离。接下来再看一段代码ThreadLocalStringdemonewThreadLocal();// 直接添加valuedemo.set(hello world);不知道你在平时使用的时候有没有想过为啥在ThreadLocal set数据的时候不用指定key呢其实就是上面提到的源码中特意将ThreadLocal的实例this作为Map的key那为什么要这么设计呢再来看一段代码在同一线程下创建了两个不同类型不同作用的ThreadLocal他们之间的值并不会互相干扰其实就是因为使用this当前ThreadLocal对象不同实例有自己的独立空间作为了ThreadLocalMap的key如果使用比如字符串作为key就可能出现key冲突当然这里还涉及到一个内存管理的问题在下一小节聊。publicstaticvoidmain(String[]args){ThreadLocalStringuserLocalnewThreadLocal();userLocal.set(jay);ThreadLocalLongtraceLocalnewThreadLocal();traceLocal.set(System.currentTimeMillis());}3.ThreadLocal会不会发生内存泄漏如果在面试的时候能把上面源码讲明白想必下一步面试官肯定会问你这个问题那就是”ThreadLocal会不会发生内存泄漏“答案是的如果使用不当是会发生内存泄漏的。要搞懂这个问题就先要了解下ThreadLocalMap的结构staticclassThreadLocalMap{// Entry继承自WeakReferenceThreadLocal?的弱引用staticclassEntryextendsWeakReferenceThreadLocal?{/** The value associated with this ThreadLocal. */Objectvalue;// Entry的构造方法中把ThreadLocal作为key给到了父类的构造方法Entry(ThreadLocal?k,Objectv){super(k);valuev;}}privatestaticfinalintINITIAL_CAPACITY16;privateEntry[]table;// Entry类型的数组privateintsize0;privateintthreshold;// 省略了一些其他方法ThreadLocalMap中维护了一个Entry类型的数组在set数据的时候是这样的顺序调用ThreadLocal的set()方法 - 调用ThreadLocalMap的set()方法 - 调用Entry的构造方法初始化数据。这个Entry类继承了一个WeakReferenceThreadLocal?的弱引用然后Entry的构造方法中把ThreadLocal作为key给到了父类的构造方法所以Entry数组对 ThreadLocal实例Key是弱引用。经过上面的分析我们得到Entry数组的keyThreadLocal实例是一个弱引用意味着如果这个 ThreadLocal实例在程序的其他地方没有被任何强引用指向了比如你将其设置为 null那么下次垃圾回收GC时这个 ThreadLocal 实例本身会被回收。此时Entry中的 Key 会变为 null。那什么情况下就是没有被强引用指向了呢声明为局部变量的时候在方法内部创建并使用 ThreadLocal未将其返回或赋值给类成员变量/静态变量当方法执行完时ThreadLocal的栈帧会被销毁。匿名对象直接使用 new ThreadLocal().set(value) 而未将 new 出来的对象赋给任何变量。非静态成员变量将 ThreadLocal 声明为某个类的普通成员变量非static当持有它的那个类实例被垃圾回收时该 ThreadLocal 实例的强引用也随之消失。我们现在已经知道Entry数组的key是一个弱引用了但Entry的value并不是一个弱引用反而它是一个强引用意味着即使 Key 已经被回收了那个存储在 Entry里的、可能非常大的 Value 对象比如一个大的缓存依然被这个 Entry强引用着只要存放它的线程还存在这个 Value 就无法被 GC 回收。下面做个总结哪些情况容易引起内存泄漏Entry数组的key被强引用也就是将ThreadLocal声明为了静态的成员变量。Entry数组的value本身就是一个强引用。使用线程池中的线程线程池中的核心线程会重复利用不会像普通线程一样被销毁。该如何有效避免内存泄漏其实解决方案就一句话但是请记住我们要的不是结果。最近看到一个很扎心的现象企业越来越关注开发效率而 AI 正在成为新的生产力工具。同样的需求会使用 AI 的工程师往往能够更快完成设计、编码和测试工作。与其担心被 AI 替代不如尽早学会驾驭 AI。最近我不仅在学习 Java 底层还在学习一些人工智能的知识发现了一个不错的 AI 学习网站内容通俗易懂比较适合程序员快速上手感兴趣的话也可以看看人工智能学习网在 ThreadLocal使用完毕后务必调用 remove() 方法来清除当前线程中存储的值最好是使用 try-finally块确保清理一定会执行。

更多文章