ThreadLocal
在多线程项目中使用很多,简化了多线程对资源的使用,
ThreadLocal
常用场景
适用于每个线程需要有自己单独的实例,实例需要在多个方法中共享,但不希望被多线程共享
- Web请求的用户身份态:Session
- 请求的链路跟踪:traceId
-
SimpleDateFormat
:因为SimpleDateFormat
不是线程安全的
ThreadLocal
实现原理
`ThreadLocal`内存布局
图中实线是引用,虚线是弱引用(不会阻止内存的回收)
-
ThreadLocal
中的数据实际上都是保存在Thread的成员变量ThreadLocal
.ThreadLocal
MapThreadLocal
s中,
ThreadLocalMap是一个Key是
ThreadLocal`,Value是泛型T的一个Map -
ThreadLocal
本身并不存储值,只是作为一个ThreadLocal
Map中的一个key - Hash冲突的解决方法:开放定址法(跟HashMap使用列表法和红黑树不同)
ThreadLocal
为什么会内存泄漏
从表面上看内存泄漏的根源在于使用了弱引用。网上的文章大多着重分析ThreadLocal
使用了弱引用会导致内存泄漏,但是另一个问题也同样值得思考:为什么使用弱引用而不是强引用?
我们先来看看官方文档的说法:
To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys.
为了应对非常大和长时间的用途,哈希表使用弱引用的 key。
下面我们分两种情况讨论:
- key 使用强引用:引用的
ThreadLocal
的对象被回收了,但是ThreadLocal
Map还持有ThreadLocal
的强引用,如果没有手动删除,ThreadLocal
不会被回收,导致Entry内存泄漏。 - key 使用弱引用:引用的
ThreadLocal
的对象被回收了,由于ThreadLocal
Map持有ThreadLocal
的弱引用,即使没有手动删除,ThreadLocal
也会被回收。value在下一次ThreadLocal
Map调用set,get,remove的时候会被清除。
比较两种情况,我们可以发现:由于ThreadLocal
Map的生命周期跟Thread
一样长,如果都没有手动删除对应key,都会导致内存泄漏,但是使用弱引用可以多一层保障:弱引用ThreadLocal
不会内存泄漏,对应的value在下一次ThreadLocal
Map调用set,get,remove的时候会被清除。
因此,ThreadLocal
内存泄漏的根源是:由于ThreadLocal
Map的生命周期跟Thread
一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用。
因此,ThreadLocal
内存泄漏的根源是:由于ThreadLocal
Map的生命周期跟Thread
一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用。
自问自答
-
为什么数据没有保存在
ThreadLocal
,而是Thread
?因为线程私有数据应该跟线程生命周期一致
-
为什么
Entry.value
不使用WeakReference
?因为
ThreadLocal
可能还存在强引用,同时value没有外部的强引用,如果设置为WeakReference
就可能导致ThreadLocal
.get()拿不到对应的value了
最佳实践
- 使用完
ThreadLocal
后,执行remove操作,避免出现内存溢出情况。特别是在线程复用的场景,不但可以避免内存溢出,还避免了数据的错误复用
网友评论