理解ThreadLocal

程序中需要全局变量,想要直接用一个全局的变量,但涉及到多线程的问题,改用ThreadLocal ,codeReview 时,被指出没有显示的调用 ThreadLocal 的remove 方法,可能会造成内存泄漏。但是对ThreadLocal 为什么会造成内存泄漏并不了解。之前一个项目重构时,重构前ThreadLocal的使用,并没有花时间去了解,这里记录下自己通过资料对 ThreadLocal 的理解。

什么是ThreadLocal

ThreadLocal 类可以提供线程内变量,即多线程中每个线程中该变量都是相互隔离的,一个线程对自己线程中变量的修改,不会影响到其它线程。

使用

get

获取当前线程对应变量的值

set

设置当前线程对应变量的值

remove

删除 ThreadLocal 中该线程的绑定的值

如何实现线程隔离

ThreadLocal 如何实现线程中的变量相互隔离的呢?

  • 比较简单的方法就是在ThreadLocal中设置一个map,每个线程作为key,保存各自的变量
  • 另一种就是,每个Thread中保存一个map,每个ThreadLocal 对象作为key

JDK 早期版本是使用第一种方案,现在使用第二种方案。

源码解析

ThreadLocal 的源码(jdk1.7):

get 方法
public T get() {
        Thread t = Thread.currentThread();// 1, 拿到当前线程
        ThreadLocalMap map = getMap(t); // 2, 拿到当前线程中的 threadLocalMap
        if (map != null) {              // 3, 若map不为空,将map 中key 为当前Thread实例的value取出,并返回
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue(); // 4, 如果map 为空,会将value设置初始值,并放入map中,最终返回初始值
    }

getMap 源码:

 ThreadLocalMap getMap(Thread t) {
        return t.threadLocals; // 是Thread 类中的一个ThreadLocal.ThreadLocalMap 类型的变量
    }

Thread 类中的 threadLocals

    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

这里可以看出,Map类型虽然是 ThreadLocal 中的ThreadLocalMap, 但是该变量是保存在 Thread中的。

setInitialValue 源码:

 private T setInitialValue() {
        T value = initialValue(); // 1, 初始化 value
        Thread t = Thread.currentThread(); // 2, 获取当前 thread
        ThreadLocalMap map = getMap(t); //3, 依旧去拿thread 中的map
        if (map != null)                //4, map 存在,将初始值放入map中
            map.set(this, value);
        else
            createMap(t, value);      //5,map不存在,创建并设置key value
        return value;                 //6, 返回value
    }

其中initialValue(可以重写该方法) :

protected T initialValue() {
        return null;
    }
set 方法
   public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

过程与setInitialValue 方法类似。

remove 方法
  public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this); // 将 Thread 中 ThreadLocalMap 中该 ThreadLocal 对象的entry 移除
     }

何时会发生内存泄漏

为什么需要显示的将一个线程中 ThreadLocalMap 中该 ThreadLocal entry 删除呢
ThreadLocalMap 中的Entry 对 Key使用的是WeakReference,ThreadLocalMap 在getEntry 以及 set 方法中,发现key 为null 的entry 会将其删除掉。

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

            Entry(ThreadLocal k, Object v) {  // 这里 Entry 中并没有保存key,而是将其 与ReferenceQueue 关联,弱引用一但GC 就会被回收
                super(k);
                value = v;
            }
        }
        .....
    }

这里写图片描述

当没有对ThreadLocal 实例的强引用后,它后被回收掉,此时ThreadLocalMap 就存在了 key 为null 的entry。但是该map 只有在调用 set 或者 getEntry 方法时,才会移除这种 entry。 在使用了线程池的情况下,线程不会结束,此时 Thread -> ThreadLocalMap -> Entry -> value -> Object value 的值一直会被强引用,不会被回收,造成内存泄漏。

http://blog.csdn.net/imzoer/article/details/7996400
https://juejin.im/entry/5662895900b0bf3758a69736
http://www.cnblogs.com/onlywujun/p/3524675.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值