ThreadLocal详解

get和set方法 

想了解threadlocal,先来看看threadlocal的get和set方法。


    /**
     * Sets the current thread's copy of this thread-local variable
     * to the specified value.  Most subclasses will have no need to
     * override this method, relying solely on the {@link #initialValue}
     * method to set the values of thread-locals.
     *
     * @param value the value to be stored in the current thread's copy of
     *        this thread-local.
     */
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

在 set 时,首先获得当前线程对象,然后通过 getMap() 方法拿到线程的 ThreadLocalMap ,并将值存入 ThreadLocalMap 中。



    /**
     * Returns the value in the current thread's copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the {@link #initialValue} method.
     *
     * @return the current thread's value of this thread-local
     */
    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();
    }

get() 方法先取得当前线程的 ThreadLocalMap 对象,然后通过将自己作为 key 取得内部的实际数据。

ThreadLocal内存泄漏

key=ThreadLocal是强引用

  • 假设在业务代码中使用完ThreadLocal, ThreadLocal ref被回收了
  • 但是因为threadLocalMap的Entry强引用了threadLocal(key就是threadLocal), 造成ThreadLocal无法被回收
  • 在没有手动删除Entry以及CurrentThread(当前线程)依然运行的前提下, 始终有强引用链CurrentThread Ref → CurrentThread →Map(ThreadLocalMap)-> entry, Entry就不会被回收( Entry中包括了ThreadLocal实例和value), 导致Entry内存泄漏

    也就是说: ThreadLocalMap中的key使用了强引用, 是无法完全避免内存泄漏的

key=ThreadLocal是弱引用

  • 假设在业务代码中使用完ThreadLocal, ThreadLocal ref被回收了
  • 由于threadLocalMap只持有ThreadLocal的弱引用, 没有任何强引用指向threadlocal实例(这里Entry不再强引用ThreadLocal了), 所以threadlocal就可以顺利被gc回收, 此时Entry中的key = null
  • 在没有手动删除Entry以及CurrentThread依然运行的前提下, 也存在始终有强引用链CurrentThread Ref → CurrentThread →Map(ThreadLocalMap)-> entry,value就不会被回收, 而这块value永远不会被访问到了(因为key=null), 导致value内存泄漏

     也就是说: ThreadLocalMap中的key使用了弱引用, 也有可能内存泄漏

内存泄漏的真实原因

 

比较以上两种情况,我们就会发现:
内存泄漏的发生跟 ThreadLocalIMap 中的 key 是否使用弱引用是没有关系的。那么内存泄漏的的真正原因是什么呢?

细心的同学会发现,在以上两种内存泄漏的情况中.都有两个前提:主要两个原因
1 . 没有手动侧除这个 Entry
2 . CurrentThread 当前线程依然运行

  • 第一点很好理解,只要在使用完下 ThreadLocal ,调用其 remove 方法删除对应的 Entry ,就能避免内存泄漏。
  • 第二点稍微复杂一点,由于ThreadLocalMap 是 Thread 的一个属性,被当前线程所引用,所以ThreadLocalMap的生命周期跟 Thread 一样长。那么在使用完 ThreadLocal 的使用,如果当前Thread 也随之执行结束, ThreadLocalMap 自然也会被 gc 回收,从根源上避免了内存泄漏。

综上, ThreadLocal 内存泄漏的根源是:
由于ThreadLocalMap 的生命周期跟 Thread 一样长,如果没有手动删除(remove()方法)对应 key 就会导致内存泄漏.

key要使用弱引用

为什么使用弱引用,根据刚才的分析,我们知道了:

无论 ThreadLocalMap 中的 key 使用哪种类型引用都无法完全避免内存泄漏,跟使用弱引用没有关系。

​ 要避免内存泄漏有两种方式:

​ 1 .使用完 ThreadLocal ,调用其 remove 方法删除对应的 Entry
​ 2 .使用完 ThreadLocal ,当前 Thread 也随之运行结束
相对第一种方式,第二种方式显然更不好控制,特别是使用线程池的时候,线程结束是不会销毁的.

也就是说,只要记得在使用完ThreadLocal 及时的调用 remove ,无论 key 是强引用还是弱引用都不会有问题.

那么为什么 key 要用弱引用呢

事实上,在 ThreadLocalMap 中的set/getEntry 方法中,会对 key 为 null(也即是 ThreadLocal 为 null )进行判断,如果为 null 的话,那么会把 value 置为 null 的.

​ 这就意味着使用完 ThreadLocal , CurrentThread 依然运行的前提下.就算忘记调用 remove 方法,弱引用比强引用可以多一层保障:弱引用的 ThreadLocal 会被回收.对应value在下一次 ThreadLocaIMap 调用 set/get/remove 中的任一方法的时候会被清除,从而避免内存泄漏.

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值