JDK ThreadLocal

2019-03-29 周五 10:45 下午

  1. ThreadLocal 回顾:
    主要看 ThreadLocal#get()方法:
    public T get() {
        Thread t = Thread.currentThread();
        // 拿到当前线程中保存的 map
        ThreadLocalMap map = getMap(t); 
        if (map != null) {
            // 以 this (也就是用户创建的 ThreadLocal 对象)为 key
            // 获取到一个 Entry 对象,然后返回 value
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        // 当 map 为 null 时,调用初始化方法,包括 ThreadLocalMap 的创建
        // 以及初始化 value
        return setInitialValue();
    }
    

ThreadLocal 示意图

  1. 内存泄漏相关
    ThreadLocalMap 中的键值对是用 Entry 存储的,Entry 继承了 WeakReference 代码如下:

        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;
    
            Entry(ThreadLocal<?> k, Object v) {
                super(k); // 调用父类构造方法,弱引用
                value = v;// 强引用
            }
        }
    

    从构造方法中可以看到 entry 对 k 的引用是一个弱引用,对 v 的引用则是强引用。
    对 k 使用弱引用的原因是:当 map 外部没有对 ThreadLocal 对象的强引用时,说明这个 ThreadLocal 对象不需要了,k 在下一次 GC 时就会被回收。 如果不采用弱引用,因为某些线程的 map 对 ThreadLocal 对象还有强引用,导致其无法被回收掉。

    虽然对 k 使用了弱引用解决了前面提到的内存泄漏的问题,但是有引发了另一个内存泄漏的问题:
    如果线程一直在运行,那么该线程持有的 map 就不会被销毁,当外部没有对 ThreadLocal 对象的强引用时,经过一轮 GC,对应的 Entry 就变成了 null -> value 的情况,对 value 的引用是强引用,这就导致 Entry 对象始终无法释放掉。
    针对这个问题:在 ThreadLocalMap 的 set() 和 rehash()等方法中,会将 key 为 null 的 Entry 的 value 置为 null,使得 Entry 对象可以被回收。

    理论上来讲,ThreadLocal 还是可能会出现内存泄漏的,因为最后将 Entry 的 value 置为 null 的操作,不够及时(待确认)。
    建议:

    1. 一直持有 ThreadLocal 对象的强引用
    2. 如果当前线程不需要本地变量时,显示的调用 Threadloca 对象的 remove()方法

参考

posted @ 2020-09-19 09:34  关小曦  阅读(115)  评论(0编辑  收藏  举报