Threadlocal扩容或者清理的逻辑是怎么样的
ThreadLocal
是 Java 中用于提供线程局部变量的类,它内部使用 ThreadLocalMap
来存储数据。下面详细介绍 ThreadLocal
的扩容和清理逻辑。
ThreadLocalMap
数据结构
ThreadLocalMap
是 ThreadLocal
的一个静态内部类,它使用一个 Entry
数组来存储键值对,其中键是 ThreadLocal
对象,值是对应的线程局部变量。Entry
类继承自 WeakReference<ThreadLocal<?>>
,这意味着 ThreadLocal
作为弱引用存储,当没有强引用指向 ThreadLocal
对象时,它会被垃圾回收。
扩容逻辑
触发条件
当 ThreadLocalMap
中的元素数量达到阈值时,会触发扩容操作。阈值的计算公式为:threshold = len * 2 / 3
,其中 len
是 Entry
数组的长度。也就是说,当元素数量超过数组长度的三分之二时,会进行扩容。
扩容步骤
- 创建新数组:将
Entry
数组的长度扩大为原来的两倍。 - 重新哈希:遍历旧数组中的所有
Entry
,将它们重新哈希到新数组中。由于数组长度发生了变化,每个Entry
的存储位置也会相应改变。 - 更新引用:将
ThreadLocalMap
的table
引用指向新数组。
以下是 ThreadLocalMap
扩容的部分源码:
private void resize() {
Entry[] oldTab = table;
int oldLen = oldTab.length;
int newLen = oldLen * 2; // 数组长度扩大为原来的两倍
Entry[] newTab = new Entry[newLen];
int count = 0;
for (int j = 0; j < oldLen; ++j) {
Entry e = oldTab[j];
if (e != null) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null; // Help the GC
} else {
int h = k.threadLocalHashCode & (newLen - 1);
while (newTab[h] != null)
h = nextIndex(h, newLen);
newTab[h] = e;
count++;
}
}
}
setThreshold(newLen);
size = count;
table = newTab;
}
清理逻辑
弱引用与内存泄漏问题
由于 Entry
中的 ThreadLocal
是弱引用,当外部没有强引用指向 ThreadLocal
对象时,它会被垃圾回收。但此时 Entry
中的 value
仍然是强引用,可能会导致内存泄漏。为了解决这个问题,ThreadLocalMap
提供了清理机制。
清理时机
- 探测式清理:在插入、查找和删除操作时,如果发现某个
Entry
的ThreadLocal
已经被垃圾回收(即Entry.get()
返回null
),会触发探测式清理,将该Entry
及其后续连续的过期Entry
清理掉。 - 启发式清理:在插入操作时,如果发现清理操作没有清理掉足够多的过期
Entry
,会触发启发式清理,尝试清理更多的过期Entry
。 - 扩容时清理:在扩容操作之前,会先进行一次清理操作,以确保数组中没有过多的过期
Entry
。
清理步骤
- 遍历数组:从当前位置开始,向后遍历
Entry
数组。 - 检查过期
Entry
:如果发现某个Entry
的ThreadLocal
已经被垃圾回收,将该Entry
的value
置为null
,并将该Entry
从数组中移除。 - 重新哈希:如果移除了某个
Entry
,后续的Entry
可能需要重新哈希,以确保数组的连续性。
以下是 ThreadLocalMap
探测式清理的部分源码:
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// 清理当前过期的 Entry
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
// 重新哈希后续的 Entry
Entry e;
int i;
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
tab[i] = null;
// 找到新的位置
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
综上所述,ThreadLocal
通过 ThreadLocalMap
实现了扩容和清理机制,以确保存储效率和避免内存泄漏。在使用 ThreadLocal
时,建议在不需要使用时手动调用 remove()
方法,以避免潜在的内存泄漏问题。