Thread、ThreadLocal、ThreadLocalMap的联系
问题:一个thread中存储有一个threadLocalMap,一个map中存储着threadLocal变量,一个thread可以有多个threadLocal变量,一个ThreadLocal变量中可以存储多个值,这理解对吗?
你的理解大部分是正确的,但需要稍作修正和补充。以下是分点解释:
1. Thread 与 ThreadLocalMap 的关系
- 每个线程(Thread)内部维护一个
ThreadLocalMap
1
2
3class Thread {
ThreadLocal.ThreadLocalMap threadLocals = null; // 存储线程私有变量
}- 每个线程(Thread)实例都有一个独立的
ThreadLocalMap
,用于存储该线程的所有ThreadLocal
变量。 - 此 Map 是懒加载的,首次调用
ThreadLocal.get()
或set()
时才会创建。
- 每个线程(Thread)实例都有一个独立的
2. ThreadLocalMap 的结构
- 键(Key)是
ThreadLocal
实例,值(Value)是该ThreadLocal
在当前线程中存储的值1
2
3
4
5
6
7
8
9class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value; // 实际存储的值
Entry(ThreadLocal<?> k, Object v) {
super(k); // 弱引用指向 ThreadLocal 实例
value = v;
}
}
}- 一个
ThreadLocalMap
可以存储多个ThreadLocal
变量:例如,线程可以同时持有ThreadLocal<User>
和ThreadLocal<Cart>
,分别存储用户信息和购物车信息。 - 每个
ThreadLocal
实例在同一个线程中只能存储一个值:多次调用threadLocal.set(value)
会覆盖旧值。
- 一个
3. ThreadLocal 的作用域
- 一个
ThreadLocal
实例可以跨多个线程使用,但每个线程中存储的值是独立的1
2
3
4
5
6
7
8
9
10
11ThreadLocal<String> userId = new ThreadLocal<>();
// 线程1
new Thread(() -> {
userId.set("User1"); // 线程1的 userId 是 "User1"
}).start();
// 线程2
new Thread(() -> {
userId.set("User2"); // 线程2的 userId 是 "User2"
}).start();- 跨线程共享同一个
ThreadLocal
实例:所有线程都可以使用同一个ThreadLocal
对象(如userId
),但每个线程通过它访问的是自己独立的值。 - 每个线程的值互不干扰:线程1的
userId
与线程2的userId
完全独立。
- 跨线程共享同一个
4. 关于“一个 ThreadLocal 变量存储多个值”的误解
- 直接存储多个值?❌
ThreadLocal
的set(value)
方法每次会覆盖旧值。例如:1
2
3ThreadLocal<List<String>> listHolder = new ThreadLocal<>();
listHolder.set(new ArrayList<>());
listHolder.get().add("A"); // 通过 List 间接存储多个值- 间接存储多个值?✅
可以将集合(如List
、Map
)作为ThreadLocal
的值,但需注意线程安全和内存泄漏问题。
- 间接存储多个值?✅
5. 内存泄漏风险
ThreadLocalMap
的键是弱引用,值是强引用- 键(
ThreadLocal
实例):弱引用,当ThreadLocal
实例被垃圾回收后,Map 中的键会变为null
,但值仍然存在。 - 值:强引用,如果不手动调用
ThreadLocal.remove()
,即使ThreadLocal
实例被回收,值也无法释放,导致内存泄漏。
- 键(
总结
正确理解
- 一个线程(Thread)拥有一个
ThreadLocalMap
。 - 一个
ThreadLocalMap
存储多个ThreadLocal
变量(键)及其对应的值。 - 一个
ThreadLocal
实例在多个线程中共享,但每个线程中存储的值独立。 - 一个
ThreadLocal
变量在同一个线程中只能直接存储一个值,但可通过集合类型间接存储多个值。
- 一个线程(Thread)拥有一个
注意事项
使用后务必调用ThreadLocal.remove()
清理数据,避免内存泄漏。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Torch's blog!