Threadlocal扩容或者清理的逻辑是怎么样的

ThreadLocal 是 Java 中用于提供线程局部变量的类,它内部使用 ThreadLocalMap 来存储数据。下面详细介绍 ThreadLocal 的扩容和清理逻辑。

ThreadLocalMap 数据结构

ThreadLocalMapThreadLocal 的一个静态内部类,它使用一个 Entry 数组来存储键值对,其中键是 ThreadLocal 对象,值是对应的线程局部变量。Entry 类继承自 WeakReference<ThreadLocal<?>>,这意味着 ThreadLocal 作为弱引用存储,当没有强引用指向 ThreadLocal 对象时,它会被垃圾回收。

扩容逻辑

触发条件

ThreadLocalMap 中的元素数量达到阈值时,会触发扩容操作。阈值的计算公式为:threshold = len * 2 / 3,其中 lenEntry 数组的长度。也就是说,当元素数量超过数组长度的三分之二时,会进行扩容。

扩容步骤

  1. 创建新数组:将 Entry 数组的长度扩大为原来的两倍。
  2. 重新哈希:遍历旧数组中的所有 Entry,将它们重新哈希到新数组中。由于数组长度发生了变化,每个 Entry 的存储位置也会相应改变。
  3. 更新引用:将 ThreadLocalMaptable 引用指向新数组。

以下是 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 提供了清理机制。

清理时机

  • 探测式清理:在插入、查找和删除操作时,如果发现某个 EntryThreadLocal 已经被垃圾回收(即 Entry.get() 返回 null),会触发探测式清理,将该 Entry 及其后续连续的过期 Entry 清理掉。
  • 启发式清理:在插入操作时,如果发现清理操作没有清理掉足够多的过期 Entry,会触发启发式清理,尝试清理更多的过期 Entry
  • 扩容时清理:在扩容操作之前,会先进行一次清理操作,以确保数组中没有过多的过期 Entry

清理步骤

  1. 遍历数组:从当前位置开始,向后遍历 Entry 数组。
  2. 检查过期 Entry:如果发现某个 EntryThreadLocal 已经被垃圾回收,将该 Entryvalue 置为 null,并将该 Entry 从数组中移除。
  3. 重新哈希:如果移除了某个 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() 方法,以避免潜在的内存泄漏问题。

posted on 2025-05-02 23:41  斜月三星一太阳  阅读(76)  评论(0)    收藏  举报