Java:ThreadLocal解析
- 前言
- 一、 什么是ThreadLocal?
- 二、ThreadLocal的内存泄漏问题
- 1.什么是内存泄漏?
- 2.为什么会出现内存泄漏问题?
- 3.如何解决内存泄漏问题?
- (1)ThreadLocal会自动清除key为null的value
- (2)使用完毕后及时调用ThreadLocal.remove()
- (3)把ThreadLocal设置为全局变量
- 三、Entry的key可以设置为强引用吗?
- 三、Entry的value可以设置为弱引用吗?
前言
ThreadLocal 是java中非常重要的内容,本文尽可能的全面介绍了 ThreadLocal内容,包括内存泄漏,key弱引用等问题
一、 什么是ThreadLocal?
第一种解释:ThreadLocal叫做线程变量,意思是ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的,也就是说该变量是当前线程独有的变量。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量
第二种解释:一个ThreadLocal在一个线程中是共享的,在不同线程之间又是隔离的”,每个线程都只能看到自己线程的值,这也就是 ThreadLocal的核心作用:实现线程范围的局部变量。
第三种解释:ThreadLocal 类主要解决的就是让每个线程绑定自己的值,可以将 ThreadLocal 类形象的比喻成存放数据的盒子,盒子中可以存储每个线程的私有数据。
如果你创建了一个 ThreadLocal 变量,那么访问这个变量的每个线程都会有这个变量的本地副本,这也是 ThreadLocal 变量名的由来。他们可以使用 get() 和 set() 方法来获取默认值或将其值更改为当前线程所存的副本的值,从而避免了线程安全问题。
总结:Thread线程可以拥有多个ThreadLocal维护的自己线程独享的共享变量(这个共享变量只是针对自己线程里面共享)
二、ThreadLocal的内存泄漏问题
1.什么是内存泄漏?
内存泄漏为程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,因为无论多少内存,迟早会被占光。
广义并通俗的说,就是:不再会被使用的对象或者变量占用的内存不能被回收,就是内存泄露。
2.为什么会出现内存泄漏问题?
ThreadLocal操作不当会引发内存泄露,最主要的原因在于它的内部类ThreadLocalMap中的Entry的设计。
Entry继承了WeakReference<ThreadLocal<?>>,即Entry的key是弱引用,弱引用的特点是,如果这个对象只存在弱引用,那么在下一次垃圾回收的时候必然会被清理掉。所以key会在垃圾回收的时候被回收掉, 而key对应的value则不会被回收, 这样会导致一种现象:key为null,value有值。
key为空的话value是无效数据,久而久之,value累加就会导致内存泄漏。
ThreadLocal在没有外部对象强引用时如Thread,发生GC时弱引用Key会被回收,而Value是强引用不会回收,如果创建ThreadLocal的线程一直持续运行如线程池中的线程,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露
从上图中可以看出,hreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal不存在外部强引用时,Key(ThreadLocal)势必会被GC回收,这样就会导致ThreadLocalMap中key为null, 而value还存在着强引用,只有thread线程退出以后,value的强引用链条才会断掉。
但如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:
Thread Ref --> Thread -->ThreaLocalMap --> Entry -->value
即永远无法回收,造成内存泄漏。
需要特别强调的时候,entry对于key即 threadlocal 的引用是弱引用,对 value 的引用是强引用,但是外部对于 threadlocal 的引用是强引用。
因此,当外部引用 threadlocal 的时候,threadlocal 不会被GC回收,因为有强引用,但是当外部不再引用 threadlocal 的时候,即
threadlocal = null
此时 threadlocal 就只有entry 的弱引用,会被回收
3.如何解决内存泄漏问题?
(1)ThreadLocal会自动清除key为null的value
ThreadLocal的get()、set()、remove()的时候都会清除线程ThreadLocalMap里所有key为null的value。
(2)使用完毕后及时调用ThreadLocal.remove()
emove方法会主动将当前的key和value(Entry)进行清除。
(3)把ThreadLocal设置为全局变量
ThreadLocal设置为全局变量使得它无法被GC回收(如果在成员变量 中使用就将修饰符设置为public static,ThreadLocal不会被回收也就不会存在key为null的情况, 也就不会内存泄漏)
三、Entry的key可以设置为强引用吗?
不可以!
当ThreadLocalMap的key为强引用,回收ThreadLocal时因为ThreadLocalMap还持有ThreadLocal的强引用,如果没有手动删除,ThreadLocal不会被回收,导致Entry内存泄漏
要知道,ThreadlocalMap是和线程绑定在一起的,如果这样线程没有被销毁,而我们又已经不会再某个threadlocal引用,那么key-value的键值对就会一直在map中存在,这对于程序来说,就出现了内存泄漏。
为了避免这种情况,只要将key设置为弱引用,那么当发生GC的时候,就会自动将弱引用给清理掉,也就是说:假如某个用户A执行方法时产生了一份threadlocalA,释放了threadlocalA后,作为弱引用,它会在下次垃圾回收时被清理掉。
而且ThreadLocalMap在内部的set,get和扩容时都会清理掉泄漏的Entry,内存泄漏完全没必要过于担心。
三、Entry的value可以设置为弱引用吗?
不可以!
假如value被设计成弱引用,那么很有可能当你需要取这个value值的时候,取出来的值是一个null。
你使用ThreadLocal的目的就是要把这个value存储到当前线程中,并且希望在你需要的时候直接从当前线程中拿出来,那么意味着你的value除了当前线程对它持有强引用外,理论上来说,不应该再有其他强引用,否则你也不会把value存储进当前线程。但是一旦你把本应该强引用的value设计成了弱引用,那么只要jvm执行一次gc操作,你的value就直接被回收掉了。当你需要从当前线程中取值的时候,最终得到的就是null。