ThreadLocal主要用于多线程环境下,尤其是在方法调用链特别长复杂的情况下单个线程的数据传递问题,相当于每一个线程中只有一个“全局”的变量。本质是将数据存到当前线程的threadLocals变量中,其类型为ThreadLocal.ThreadLocalMap。

弱引用

通过类似

WeakReference<SomeClass> weakRef = new WeakReference<>(someClassObj, referenceQueue);

创建的引用,如果只存在weakRef一条对于someClassObj的弱引用,那么在垃圾回收的时候回将someClassObj回收掉,weakRef.get()方法返回null,否则会返回真实的someClassObj。

ThreadLocal中的弱引用

ThreadLocalMap通过一个Entry数组维护ThreadLocal保存的值,下标通过

threadLocal.threadLocalHashCode & (len - 1)算出

Entry继承自WeakReference,而WeakReference继承自Reference,所以Entry获得了referent属性,该属性就是实际的ThreadLocal对象。通过下面的super(k)方法设置,这样Entry就持有了ThreadLocal的一个弱引用。

    // java.lang.ThreadLocal.ThreadLocalMap.Entry
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

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

而ThreadLocal得强引用就是方法中写的

ThreadLocal<SomeClass> threadLocal = new ThreadLocal<>();

获取值和内存泄露

我们看下ThreadLocal获取值的代码

    // java.lang.ThreadLocal#get
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
    
    // java.lang.ThreadLocal#getMap
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    
    // java.lang.ThreadLocal.ThreadLocalMap#getEntry
        private Entry getEntry(ThreadLocal<?> key) {
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
            if (e != null && e.get() == key)
                return e;
            else
                return getEntryAfterMiss(key, i, e);
        }

threadLocal在取值时通过自身threadLocalHashCode计算出下标然后在Entry数组中取得Entry,Entery的get方法(继承自Reference)获得ThreadLocal的软引用。

由于在获取Entry时判断e.get() == key,这样的话如果方法执行完毕,尽管Entry中的弱引用指向threadLocal,但是方法栈中threadLocal强引用不存在,threadLocal会被回收,那么在当前线程中无论如何都获取不到Entry了,即发生了内存泄露。

为了保证不会内存泄露,我们通常使用final static来修饰threadLocal,由于是同一个threadLocal,threadLocalHashCode一样,计算的Entery下标也相同,而且总是存在强引用,e.get()方法不会由于threadLocal被回收返回null,即e.get() == key始终成立,能获取到Entry值。

Entry中使用弱引用的原因

可以看到,Entry中使用弱引用的区别就是在ThreadLocal没有其他引用时,ThreadLocal作为Entry的referent属性,可以被垃圾回收,仅仅是为了这个用处,而不是因为使用了弱引用而导致内存泄露

使用static final需要注意的地方

对于线程池,比如tomcat中,每次请求后需要remove掉ThreadLocal中的内容,否则也会发生内存泄露,因为线程池中的线程可能会一直维持特别长时间,特别严重的会发生业务异常,比如第二次请求拿到了第一次请求设置的值

参考文章:

https://www.jianshu.com/p/a1cd61fa22da