ThreadLocal常用场景
适用于每个线程需要有自己单独的实例,实例需要在多个方法中共享,但不希望被多线程共享
- Web请求的用户身份态:Session
- 请求的链路跟踪:traceId
-
SimpleDateFormat:因为SimpleDateFormat不是线程安全的
ThreadLocal实现原理

图中实线是引用,虚线是弱引用(不会阻止内存的回收)
-
ThreadLocal中的数据实际上都是保存在Thread的成员变量ThreadLocal.ThreadLocalMapThreadLocals中,ThreadLocalMap是一个Key是ThreadLocal`,Value是泛型T的一个Map -
ThreadLocal本身并不存储值,只是作为一个ThreadLocalMap中的一个key - Hash冲突的解决方法:开放定址法(跟HashMap使用列表法和红黑树不同)
ThreadLocal为什么会内存泄漏
从表面上看内存泄漏的根源在于使用了弱引用。网上的文章大多着重分析ThreadLocal使用了弱引用会导致内存泄漏,但是另一个问题也同样值得思考:为什么使用弱引用而不是强引用?
我们先来看看官方文档的说法:
下面我们分两种情况讨论:
- key 使用强引用:引用的
ThreadLocal的对象被回收了,但是ThreadLocalMap还持有ThreadLocal的强引用,如果没有手动删除,ThreadLocal不会被回收,导致Entry内存泄漏。 - key 使用弱引用:引用的
ThreadLocal的对象被回收了,由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被回收。value在下一次ThreadLocalMap调用set,get,remove的时候会被清除。
比较两种情况,我们可以发现:由于ThreadLocalMap的生命周期跟Thread一样长,如果都没有手动删除对应key,都会导致内存泄漏,但是使用弱引用可以多一层保障:弱引用ThreadLocal不会内存泄漏,对应的value在下一次ThreadLocalMap调用set,get,remove的时候会被清除。
因此,ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用。
因此,ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用。
自问自答
-
为什么数据没有保存在
ThreadLocal,而是Thread?因为线程私有数据应该跟线程生命周期一致
-
为什么
Entry.value不使用WeakReference?因为
ThreadLocal可能还存在强引用,同时value没有外部的强引用,如果设置为WeakReference就可能导致ThreadLocal.get()拿不到对应的value了
最佳实践
- 使用完
ThreadLocal后,执行remove操作,避免出现内存溢出情况。特别是在线程复用的场景,不但可以避免内存溢出,还避免了数据的错误复用










