说说 ThreadLocal

按照编码建议将 ThreadLocal 对象声明为 static 的,正常使用 ThreadLocal 即可.

前两天看同事的代码,同事提了一句说

ThreadLocal 的 map 的 Entry 是 WeakReference,所以会出现 ThreadLocal 对象被 GC 释放的问题,故他在代码中使用 ThreadLocal 时考虑了这个情况,不会出现问题。

闻之大惊,因为我这么多年使用 ThreadLocal 从未考虑过这种情况,细一考量会发现如果真是这样的话,ThreadLocal 的使用场景就几乎不太存在了。直觉上感觉应该是他哪里理解错了。

今天有时间,就深究了一下这个问题。

先说结论,上面的说法确实理解有误,按照编码建议将 ThreadLocal 对象声明为 static 的,正常使用 ThreadLocal 即可。


以下是探究过程。

首先写了一个例子:

public class ThreadLocalTest {

    private static final ThreadLocal<Map<String, String>> holder = ThreadLocal.withInitial(HashMap::new);
    private static final List<ThreadLocal<Integer>> holderList = new ArrayList<>();

    public static void main(String[] args) {
        init(10000, 10000);
        gc();
        initHolders(10000);
        gc();
        System.out.println(holder.get().size());
        checkHolderList();
    }

    private static void gc() {
        System.out.println("gc goes here ...");
        System.gc();
    }

    private static void init(int size, int valueLength) {
        for (int i = 0; i < size; i++) {
            holder.get().put(String.valueOf(i), RandomStringUtils.random(valueLength));
        }
    }

    private static void initHolders(int size) {
        for (int i = 0; i < size; i++) {
            ThreadLocal<Integer> holder = new ThreadLocal<>();
            holderList.add(holder);
            holder.set(i);
        }
    }

    private static void checkHolderList() {
        System.out.println(holderList.size());
        for (int i = 0; i < holderList.size(); i++) {
            if (holderList.get(i).get() != i) {
                throw new RuntimeException("check failed");
            }
        }
        System.out.println("check success");
    }
}

//java ThreadLocalTest -verbose:gc

运行后未发现任何问题,如果将 ThreadLocal 换成 WeakReference,则测试无法运行。

那是什么原因导致的呢?看代码。

同事说的 ThreadLocal 中 Entry 为 WeakReference 的代码如下:

// TheadLocal.ThreadLocalMap.Entry
/**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

这里要注意,WeakReference 引用的对象是 ThreadLocal,也就是说只要你保持对 ThreadLocal 对象的引用,就不会被清除掉。而这里使用 WeakReference 的原因,也写的很清除了,主要是避免在 ThreadLocal 对象被清理的情况下发生内存泄漏。如果 ThreadLocal 对象被清理了,Thread 的成员变量 ThreadLocal.ThreadLocalMap threadLocals 会在 gc 时清理掉这些对象,避免内存泄漏。

另外,jdk 的实现中 ThreadLocal 设计为由 Thread 持有以 ThreadLocal为 key 的 ThreadLocalMap ,而不是由 ThreadLocal 对象持有以 Thread 为 key 的 ThreadLocalMap 也是处于避免内存泄漏的考虑。因为大部分情况下 Thread 的生命周期会短于 ThreadLocal 的生命周期。