您当前的位置: 首页 > 
  • 5浏览

    0关注

    674博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

ThreadLoacal引起的内存泄露

沙漠一只雕得儿得儿 发布时间:2017-04-09 10:14:35 ,浏览量:5

ThreadLocal是啥?

可以看到ThreadLocal是Thread内部的一个变量:

在多线程中,因为变量是可以共享的,所以就存在了线程安全问题,我们可以通过同步的方式来解决,如果我们对于单个线程的变量,为了避免线程安全,担又不想用同步的方式,我们就可以用到ThreadLocal。

ThreadLocal可以提供我们一个局部变量,而且这个变量与一般变量还不同,他是每个线程独有的,与其他线程互不干扰的。

ThreadLocal怎么用?
ThreadLocal local = new ThreadLocal();
int a = 10;//声明变量
​
​
local.set(a+10);//赋值
local.get()//取值
local.remove();//删除Entry

我们可以在线程A中对变量a进行赋值,并且通过set方法绑定到当前线程的Threadlocal,当要取值时可以通过get方法,取值=20。但是线程B通过同样的方式取值会等于null。这就体现了Threadlocal的特点,将变量独立于单个线程。与其他线程互不干扰。

ThreadLocal原理 get方法

 

我们调用get的方法时,会根据当前线程得到一个 ThreadLocalMap ,这是一个存储entry,而entry又是通过key-value(键值对)来保存的。而key就是当前线程的ThreadLocal,一个线程只有一个ThreadLocal,ThreadLocal是用final修饰的。

调用get方法就是通过ThreadLocalMap ,去获取key=当前线程的ThreadLocal对应的value。

情况①: ThreadLocalMap 为空,在没有创建ThreadLocalMap 的情况下直接调用get方法,

就会直接调用 setInitialValue 方法

在这个方法之中,首先会执行如下语句:

T value = initialValue();

所以这种情况下会返回null值

然后再执行

createMap(t, value);

此时的this就是当前线程的ThreadLocal实例,value等于null。

结论:这种没有创建ThreadLocalMap,直接调用get的结果是,最终会创建一个

ThreadLocalMap,然后往里面放个值,(key-value)key=当前实例的ThreadLocal,value=null

情况②:首先在线程A中local.set(10),再在线程A中调用local.get()获取的值=10(同一线程)

情况③:首先在线程A中local.set(10),再在线程B中调用local.get()获取的值=null(不同线程)  

set方法

我们要对一个变量赋值为10,就首先要调用set方法

 

set方法其实和get方法挺像的, 也是先拿到当前线程,然后根据当前线程得到ThreadLocalMap,这里同样之前没有,所以需要重新创建,也就是去执行:

createMap(t, value); 但是这里的value就不是null了,而是传过来的值10

又到了这里,创建了一个新的ThreadLocalMap来存放数据,this同样也是ThreadLocal的实例,也就是local,这样一来,key就对应我们的ThreadLocal实例,value就是传过来的10了,另外我们大概知道,这么个键值对是放在ThreadLocalMap中的,然后我们通过当前线程可以得到这个ThreadLocalMap,再根据ThreadLocal这个实例就可以得到value的值,也就是10.

然后再去调用get方法,就可以通过当前线程的ThreadLocal获取到对应的值啦,而且ThreadLocalMap也在set方法中创建了。  

ThreadLocal内存泄漏
threadlocal里面使用了一个存在弱引用的map,当释放掉threadlocal的强引用以后,map里面的value却没有被回收.而这块value永远不会被访问到了. 所以存在着内存泄露. 最好的做法是将调用threadlocal的remove方法.
  在threadlocal的生命周期中,都存在这些引用. 看下图: 实线代表强引用,虚线代表弱引用.
    每个thread中都存在一个map, map的类型是ThreadLocal.ThreadLocalMap. Map中的key为一个threadlocal实例. 这个Map的确使用了弱引用,不过弱引用只是针对key. 每个key都弱引用指向threadlocal. 当把threadlocal实例置为null以后,没有任何强引用指向threadlocal实例,所以threadlocal将会被gc回收. 但是,我们的value却不能回收,因为存在一条从current thread连接过来的强引用. 只有当前thread结束以后, current thread就不会存在栈中,强引用断开, Current Thread, Map, value将全部被GC回收.
  所以得出一个结论就是只要这个线程对象被gc回收,就不会出现内存泄露,但在threadLocal设为null和线程结束这段时间不会被回收的,就发生了我们认为的内存泄露。其实这是一个对概念理解的不一致,也没什么好争论的。最要命的是线程对象不被回收的情况,这就发生了真正意义上的内存泄露。比如使用线程池的时候,线程结束是不会销毁的,会再次使用的。就可能出现内存泄露。  
解决方法

每次使用完 ThreadLocal 就把对应的entry给删除掉,通过调用remove方法

这个就是根据key删除掉对应的Entry,如此一来,我们就解决了内存泄漏问题,因为可能出现内存泄漏的Entry,在我们使用完之后就立马删除了。
总结:

1、ThreadLocal是用来提供线程局部变量的,在线程内可以随时随地的存取数据,而且线程之间是互不干扰的。

2、ThreadLocal实际上是在每个线程内部维护了一个ThreadLocalMap,这个ThreadLocalMap是每个线程独有的,里面存储的是Entry对象,Entry对象实际上是个ThreadLocal的实例的弱引用,同时还保存了value值,也就是说Entry存储的是键值对的形式的值,key就是ThreadLocal实例本身,value则是要存储的数据。

3、TreadLocal的核心是底层维护的ThreadLocalMap,它的底层是一个自定义的哈希表,增长因子是2/3,增长因子也可以叫做是一个阈值,底层定义threshold,当哈希表容量大于或等于阈值的3/4的时候就开始扩容底层的哈希表数组table。

4、ThreaLocalMap中存储的核心元素是Entry,Entry是一个弱引用,所以在垃圾回收的时候,ThreadLocal如果没有外部的强引用,它会被回收掉,这样就会产生key为null的Entry了,这样也就产生了内存泄漏。

5、在ThreadLocal的get(),set()和remove()的时候都会清除ThreadLocalMap中key为null的Entry,如果我们不手动清除,就会造成内存泄漏,最佳做法是使用ThreadLocal就像使用锁一样,加锁之后要解锁,也就是用完就使用remove进行清理。

【Android面试】Android中的ThreadLocal应用_Rose J的博客-CSDN博客_android threadlocal面试

关注
打赏
1657159701
查看更多评论
立即登录/注册

微信扫码登录

0.1321s