• 来说说ThreadLocal内存溢出问题


    前言

    上次有个小伙伴问我,说他面试的时候,被问到ThreadLocal内存溢出问题,没有回答出来;那我们今天就来了解一下ThreadLocal。

    ThreadLocal介绍

    多线程在访问同一个变量时会产生线程安全问题,ThreadLocal是JDK包提供的,它提供线程本地变量,如果创建一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个副本,在实际多线程操作的时候,操作的是自己本地内存中的变量,从而规避了线程安全问题,如下图所示

    ThreadLocal使用

    开启两个线程,在每个线程内部设置了本地变量的值,然后调用print方法打印当前本地变量的值。如果在打印之后调用本地变量的remove方法会删除本地内存中的变量,代码如下所示

    执行结果

     
    
     
    

    thread1 :localVar1 thread2 :localVar2 thread1 after remove : null thread2 after remove : null

    上面代码能够看出,虽然2个线程都使用了ThreadLocal localVar变量,但是线程里面的变量互相不影响。

    ThreadLocal原理

    我们来看看ThreadLocal是怎么实现的?我们来看一下源码

    set源码

    添加图片注释,不超过 140 字(可选)

     在上面的代码中,(2)处调用getMap方法获得当前线程对应的threadLocals,该方法代码如下

     
    
     
    

    ThreadLocalMap getMap(Thread t) { return t.threadLocals; //获取线程自己的变量threadLocals,并绑定到当前调用线程的成员变量threadLocals上 }

    如果调用getMap方法返回值不为null,就直接将value值设置到threadLocals中(key为当前ThreadLocal引用,值为本地变量);如果getMap方法返回null说明是第一次调用set方法(前面说到过,threadLocals默认值为null,只有调用set方法的时候才会创建map),这个时候就需要调用createMap方法创建threadLocals,该方法如下所示

     
    
     
    

    void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }

    createMap方法不仅创建了threadLocals,同时也将要添加的本地变量值添加到了threadLocals中。

    get源码

    在get方法的实现中,首先获取当前调用者线程,如果当前线程的threadLocals不为null,就直接返回当前线程绑定的本地变量值,否则执行setInitialValue方法初始化threadLocals变量。在setInitialValue方法中,类似于set方法的实现,都是判断当前线程的threadLocals变量是否为null,是则添加本地变量(这个时候由于是初始化,所以添加的值为null),否则创建threadLocals变量,同样添加的值为null

    remove实现

    remove方法判断该当前线程对应的threadLocals变量是否为null,不为null就直接删除当前线程中指定的threadLocals变量

    溢出分析

    每个线程内部有一个名为threadLocals的成员变量,该变量的类型为 ThreadLocal.ThreadLocalMap类型(类似于一个HashMap),其中的key为当前定义的ThreadLocal变量的this引用,value为我们使用set方法设置的值。每个线程的本地变量存放在自己的本地内存变量threadLocals中,如果当前线程一直不消亡,那么这些本地变量就会一直存在(所以可能会导致内存溢出),因此使用完毕需要将其remove掉。

    看下图:

    番外篇

    1、ThreadLocalMap 内部 Entry 中 key 使用的是对 ThreadLocal 对象的弱引用,这是为了避免内存泄露,因为如果是强引用,那么即使其他地方没有对 ThreadLocal 对象的引用,ThreadLocalMap 中的 ThreadLocal 对象还是不会被回收,而如果是弱引用则这时候 ThreadLocal引用是会被回收掉的。

    2、但是对于的 value 还是不能被回收,这时候 ThreadLocalMap 里面就会存在 key 为 null 但是 value 不为 null 的 entry 项,虽然 ThreadLocalMap 提供了 set,get,remove 方法在一些时机下会对这些 Entry 项进行清理,但是这是不及时的,也不是每次都会执行的,所以一些情况下还是会发生内存泄露,所以在使用完毕后即使调用 remove 方法才是解决内存泄露的最好办法。

    3.线程池里面设置了 ThreadLocal 变量一定要记得及时清理,因为线程池里面的核心线程是一直存在的,如果不清理,那么线程池的核心线程的 threadLocals 变量一直会持有 ThreadLocal 变量。

    总结

    希望这篇文章能够帮助小伙伴们理解,这里再提供网上常用的图片,有助于小伙伴理解

    谢谢!!!

  • 相关阅读:
    C++模拟实现——红黑树封装set和map
    TensorFlow轻松入门(一)(更新中)
    Python高级二
    Linux--gcc/g++
    TypeScript断言
    制作一个简单HTML静态网页(HTML+CSS)
    matlab GPR高斯过程回归与股票价格预测
    供应商绩效管理:如何衡量以及衡量什么指标?
    异步编程详解(.NET)
    企业管理软件使用与择选时要注意五点
  • 原文地址:https://blog.csdn.net/AS011x/article/details/127105255