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() 方法,以避免潜在的内存泄漏问题。
浙公网安备 33010602011771号