【ThreadLocal】ThreadLocal实际开发中的注意点

ThreadLocal内存泄漏

内存泄漏:对象不再使用,但是仍然驻留在内存中。

ThreadLocalMap中Entry的结构

static class Entry extends WeakReference<ThreadLocal<?>> {
    Object value; // ThreadLocal变量值
    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}

ThreadLocalMap内部底层是Entry数组,Entry的key为ThreadLocal,value是线程的该ThreadLocal变量值。Entry内部类继承了WeakReference类,是弱引用。我们可以看到每个Entry同时包含了一个对key的弱引用和一个对value的强引用。

弱引用的特点是,如果这个对象只被弱引用关联(没有任何强引用关联),那么对象可以被回收。弱引用不会阻止GC。

正常情况下,当线程终止,会将线程的ThreadLocalMap值设为null。因为没有了强引用,保存在ThreadLocal中的value会被垃圾回收器回收。

强引用链

如果强引用一直在,垃圾回收器就不会被回收,因此可能导致内存泄漏。

如果采用线程池,则线程很长时间不会终止,那么key对应value就不会回收,因为有下面的调用链:

Thread -强引用-> ThreadLocalMap -强引用-> Entry(key为null)-强引用-> value

Thread和value存在这个强引用链路,导致value无法回收,从而导致OOM.

JDK中对此有做处理,在ThreadLocalMap的set、remove、rehas方法内部的resize操作中,有如下语句

if (k == null) { // 过期Entry,清空value
    e.value = null; // 利于垃圾回收

然而,如果ThreadLocal不被引用,或者不调用set、remove、rehash方法,那么调用链会一直存在,导致value的内存泄漏。

解决措施

使用完ThreadLocal后,调用remove方法,删除对应的Entry对象,避免内存泄漏。

// 此时使用完ThreadLocal,回收该ThreadLocal
UserInfoHolder.holder.remove();

线程池不适合使用ThreadLocal。

NPE问题

public class WrongWayNPE {
    private static ThreadLocal<Integer> integerThreadLocal = new ThreadLocal<>();

    public static void main(String[] args) {
        System.out.println(integerThreadLocal.get());
        System.out.println(getThreadLocal());
    }

    static int getThreadLocal() {
        return integerThreadLocal.get();
    }
}

如代码中ThreadLocal的泛型类型是Integer类型,未初始化值直接调get()方法,得到的值是null。null无法拆箱装换成基本类型,引发空指针异常。

因此,我们了解NPE问题不是ThreadLocal的问题,而是开发人员代码问题。

你可能感兴趣的:(#,并发内参)