Threadlocal中的弱引用到底是怎么一回事

        最近在复习面试资料的时候,偶然间看到了有人觉得Threadlocal的一个缺点是内存泄漏,顺着他的博客往下看,他就是觉得在entry中的key存在着对threadlocal实例对象的弱引用,然后就觉得在一次gc以后会因为key为null但是value还有值从而造成内存泄漏。

        我左思右想,还是觉得不对劲,再查看相关文献和源码的时候才恍然大悟——人家早就是有预谋的使用弱引用好吗。。。可是说到这里有人会说这个人到底在讲什么??那么现在我们理顺思路重新讲呗

什么是Threadlocal

ThreadLocal 是 JDK java.lang 包下的一个类,是天然的线程安全的类

在源码中是这样说的:ThreadLocal提供线程局部变量。这些变量与正常的变量不同,因为每一个线程在访问ThreadLocal实例的时候(通过其get或set方法)都有自己的、独立初始化的变量副本。ThreadLocal实例通常是类中的私有静态字段,使用它的目的是希望将状态(例如,用户ID或事务ID)与线程关联起来。

Threadlocal的原理实现

这里我先说一下,虽然他提供线程局部变量,但我还是觉得他就是一个对Thread类中一些数据的小偷(因为弱引用偷完就消失不见了hhhh)。它会将数据存放到Thread对象的ThreadlocalMap的entry中(有一点套娃的感觉,都是内部类套内部类)

这里简单放一下他的源码实现,引出下面的set和get。

//获取下一个ThreadLocal实例的哈希数
private final int threadLocalHashCode = nextHashCode();
//原子计数器,主要到它被定义为静态
private static AtomicInteger nextHashCode =
        new AtomicInteger();
//哈希数(增长数),也是带符号的32位整型值黄金分割值的取正
private static final int HASH_INCREMENT = 0x61c88647;
//生成下一个哈希数
private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }
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();
    }
public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            //threadLocals属性不为null则覆盖key为当前的ThreadLocal实例,值为value
            map.set(this, value);
        } else {
            //threadLocals属性为null,则创建ThreadLocalMap,第一个项的Key为当前的ThreadLocal实例,值为value
            createMap(t, value);
        }
    }
ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

可以看出来他的set和get都是操作的Thread中的数据(这里更正一下,应该是桥梁而不是小偷hhh)

Threadlocal中的弱引用到底是怎么一回事_第1张图片

Threadlocal中的弱引用到底是怎么一回事_第2张图片 上面就是我从其他博主那里爬过来的流程图(忘记具体地址了!sorry~)

回归正题

说到这里朋友们是不是能有一点理解我一开始提出的问题了呀~

其实ThreadLocal本身不存放任何的数据,而ThreadLocal中的数据实际上是存放在线程实例中,从实际来看是线程内存泄漏,底层来看是Thread对象中的成员变量threadLocals持有大量的K-V结构,并且线程一直处于活跃状态导致变量threadLocals无法释放被回收。threadLocals持有大量的K-V结构这一点的前提是要存在大量的ThreadLocal实例的定义,一般来说,一个应用不可能定义大量的ThreadLocal,所以一般的泄漏源是线程一直处于活跃状态导致变量threadLocals无法释放被回收。但是我们知道,ThreadLocalMap中的Entry结构的Key用到了弱引用(·WeakReference>·),当没有强引用来引用ThreadLocal实例的时候,JVM的GC会回收ThreadLocalMap中的这些Key,此时,ThreadLocalMap中会出现一些Key为null,但是Value不为null的Entry项,这些Entry项如果不主动清理,就会一直驻留在ThreadLocalMap中。也就是为什么ThreadLocal中get()、set()、remove()这些方法中都存在清理ThreadLocalMap实例key为null的代码块。

上面是不是你们觉得的缺点?漏~

ThreadLocal中一个设计亮点是ThreadLocalMap中的Entry结构的Key用到了弱引用。试想如果使用强引用,等于ThreadLocalMap中的所有数据都是与Thread的生命周期绑定,这样很容易出现因为大量线程持续活跃导致的内存泄漏。使用了弱引用的话,JVM触发GC回收弱引用后,ThreadLocal在下一次调用get()、set()、remove()方法就可以删除那些ThreadLocalMap中Key为null的值,起到了惰性删除释放内存的作用。

其次就是在ThreadLocalMap会存在相应的遍历来查看key为null的槽位并且进行删除哦~

综上所述,我并不觉得这是一个缺陷,反而是一个很灵活的点~因为我们可以手动将弱引用变成强引用嘛(static关键字了解一下~)

你可能感兴趣的:(java,后端)