ThreadLocal及其内存泄漏问题

在多线程并发访问同一个共享变量的时,如果不做同步控制,可能会出现数据不一致的问题,
所以需要用 synchronized 关键字加锁来解决。
而 ThreadLocal 则换了一个思路来处理多线程的问题。ThreadLocal 本身不存储数据,当调用ThreadLocal 的 set<T value> 方法时,ThreadLocal 会将自身的引用 this 作用为 key,将用户传入的值作为 value,存到线程的 ThreadLocalMap 中,这就相当于每个线程的读写操作都基于其本身的私有副本,而线程之间的数据是相互隔离的。基于ThreadLocal的操作也就不存在线程安全问题了,它相对采用了拿空间换时间的思路,从而提高程序的执行效率。

四种引用对象
在 ThreadLocalMap 内部维护了一个 Entry 数组,它的属性名字叫table,它用来存储键值对的映射
关系,代码如下:

static class ThreadLocalMap {
        ...
        private Entry[] table;
        ...
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

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


Entry 将 ThreadLocal 作为key,将 Object 作为值,它继承自 WeakReference ,构造函数的第一行代码是 super(k);这就意味着 ThreadLocal 对象是一个弱引用。
在JDK1.2之后,Java做了一些对象引用的扩充,将引用关系由强到弱依次分为强、软、弱、虚:
强引用:只要强引用关系(指代码中普遍存在的赋值关系)还在,对象就永远不会被回收(男主角,有主角光环,怎么都死不了);
软引用:当内存溢出时会被GC回收(女主角,虽然中间经历过一段,但还是会死);
弱引用:不管内存是否够用,下一次都会被GC回收(男二号,注定是用来牺牲的);
虚引用:等同于没有引用,回收会收到一个系统通知(路人甲)。

ThreadLocal中的对象引用关系:
 

内存泄漏问题和 ThreadLocalMap 中的 Entry 有很大的关系,由于 ThreadLocal 对象是弱引用,如果外部没有强引用指向它,它就会被 GC 回收,导致 Entry 的 key 为空,但由于 Entry 对象还在强引用 value,导致 value 无法被回收,内存泄漏就可能发生,Entry 对象属于 ThreadLocalMap,而ThreadLocalMap 有属于 Thread,如果线程本身生命周期很短,只要线程被销毁,value就会被回收掉,但问题是,线程是非常珍贵的计算机资源,很少会去频繁的创建和销毁它,一般是通过线程池去使用,这就大大拉长了线程的生命周期,而内存泄漏带来的影响将会越来越大。

解决办法(规范使用):
1.每次用完 ThreadLocal 及时调用 remove() 方法进行数据清除;
2.将 ThreadLocal 变量尽可能的定义成 static final (避免频繁创建 ThreadLocal 实例)。

ThreadLocal的内部优化:
1.调用 set() 方法时,会采样清理,全量清理,扩容时还会继续检查;
2.调用 get() 方法时,没有直接命中,向后环形查找时会进行清理;
3.调用 remove() 方法时,除了清除当前Entry,还会继续向后清理。

  • 8
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值