1.一个通俗的比喻
-
线程(Thread):好比去健身房锻炼的用户,每个人有一个自己的大储物柜
ThreadLocalMap
。 -
ThreadLocalMap
:用户的储物柜,上面有多个抽屉。 -
ThreadLocal
实例:储物柜上抽屉的钥匙;一个实例就是一个钥匙,对应一个抽屉;钥匙是公共的,但用户只能拿该钥匙去打开自己的抽屉。 -
Entry
:储物柜上的抽屉(抽屉上有对应钥匙的编号)。
2. 关系拆解
① 用户和储物柜
-
每个用户(线程)都有一个储物柜(ThreadLocalMap)。
-
ThreadLocalMap是线程私有的,不允许其他线程访问。
② 储物柜和抽屉
-
储物柜(ThreadLocalMap)上有很多抽屉(Entry数组)——>ThreadLocalMap是一个Entry数组。
-
每个抽屉里存放一个物品(值),抽屉上贴着钥匙的编号(ThreadLocal实例)。
也就是说,Entry类其实封装着一个键值对,Key是ThreadLocal实例,value是存储的值;
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
③ 钥匙和抽屉
-
用户用钥匙(ThreadLocal实例)打开储物柜里的对应抽屉。
-
不同用户用同一把钥匙,打开的其实是各自储物柜里对应的抽屉,互不影响!
3. 其他设计细节
为什么用弱引用(WeakReference)?
-
Entry的Key(ThreadLocal)是弱引用:
-
如果
lockerKey1
在代码中被置为null
,垃圾回收时,Entry中的Key会被清除(防止内存泄漏)。 -
但Value仍是强引用,所以必须手动调用
remove()
清理!(否则Value可能一直存在)
-
哈希冲突解决
-
ThreadLocalMap
使用开放寻址法(而非HashMap的链表):-
如果抽屉被占用了(发生哈希冲突),就往后继续遍历数组,找下一个空抽屉。
-
取数据时,先找到初始位置,再线性探测。
-
总结
简单来说,就是对于同一个ThreadLocal
对象,以它作为键(Key),在不同线程的ThreadLocalMap
中存储的值是不同的。
因此实现了在 Java 里,ThreadLocal
类为每个使用该变量的线程都单独创建一个独立的副本,每个线程都能独立地改变自己的副本,而不会影响其他线程所对应的副本。