ThreadLocal并不是为了解决多线程访问共享资源的问题。而是为每个线程提供独立的变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个方法或者组件之间参数传递的复杂度。

1、 Thread,ThreadLocalMap,ThreadLocal之间的关系

    每个Thread持有一个ThreadLocalMap对象,ThreadLocalMap对象以ThreadLocal对象作为key,value为将要存储的值。

    通过ThreadLocal可获得当前Thread的ThreadLocalMap,以ThreadLocal为key可获得当前线程的ThreadLocal值。

    获取过程ThreadLocal——>ThreadLocalMap——>Entry——>value

2、get过程

1、获取当前线程的ThreadLocalMap,以当先ThreadLocal对象为key获取值。

2、若当前线程的ThreadLocalMap对象为null,先创建当前线程的ThreadLocalMap,并初始化当前ThreadLocal对应value值,默认将value初始化为null,即initialValue()将返回null,将<ThreadLocal,value>放入ThreadLocalMap后,返回value。

理解ThreadLocal

getMap()方法

理解ThreadLocal

3、set过程

1、当前线程的ThreadLocalMap存在,存入<ThreadLocal,value>

2、当前线程的ThreadLocalMap不存在,创建后进行1

理解ThreadLocal

4、内存泄漏问题

ThreadLocal中存在以下引用关系

Thread——>ThreadLocalMap——>Entry——>ThreadLocal

Thread——>ThreadLocalMap——>Entry——>value

ThreadLocal与value的引用存在以上引用链,在没有外部强引用的情况下(ThreadLocal=null,value=null),ThreadLocal与value不会被GC回收,如果线程一直不被销毁,则引用链将一直存在,造成内存泄漏。

1、ThreadLocal对象造成的内存泄漏(外部引用ThreadLocal=null时存在

理解ThreadLocal

可以看到,ThreadLocalMap使用ThreadLocal的弱引用作为key,这样可以保证如果一个ThreadLocal没有外部强引用引用它,会被GC回收。避免的ThreadLocal对象的内存泄漏。

2、value造成的内存泄漏(外部强引用value=null时存在

当线程一直存在时,由于引用链一直存在,且对value没有ThreadLocal一样的弱引用处理机制,则value一直无法被回收,造成内存泄漏。

补充一点,当ThreadLocalMap的key为null,value的外部强引用存在时,此时value对象不存在内存泄漏(毕竟还有强引用在使用它,本就不应该被回收。而Entry却存在。此时key=null,该value也不能再被访问。该Entry占用的内存实际是没有意义的,应该被回收,但ThreadLocalMap还对此Entry有依赖,无法回收Entry,造成内存泄漏。

所以:

ThreadLocal=null,value=null,Entry与value都存在内存泄漏。

ThreadLocal=null,value!=null,只有Entry存在内存泄漏。

但不管是Entry还是value的内存泄漏,只要将key为null的Entry移除就可以解决问题。

关于Entry造成的内存泄漏是我的理解,因为很多文章都说内存泄漏是因为value造成的,但value外部强引用存在的情况下并不是这样。

ThreadLocalMap对以上问题也做了解决,ThreadLocalMap的getEntry与set方法中会对其遇到的key=null的Entry进行移除,防止内存泄漏。

理解ThreadLocal

set方法:

理解ThreadLocal

但上面的设计思路依赖一个前提条件:要调用ThreadLocalMap的getEntry函数或者set函数,且如果一些key=null的Entry在某次getEntry和set方法未被遇到,内存泄漏仍存在(除非对每个key调用一次),所以很多情况下需要使用者手动调用ThreadLocal的remove函数,删除不再需要的ThreadLocal,防止内存泄露。

事实上,只要这个线程对象被gc回收,就不会出现内存泄露,但在threadLocal设为null和线程结束这段时间不会被回收的,就发生了我们认为的内存泄露。最要命的是线程对象不被回收的情况,这就发生了真正意义上的内存泄露。比如使用线程池的时候,线程结束是不会销毁的,会再次使用的。就可能出现内存泄露。

参考:

彻底理解ThreadLocal

ThreadLocal 内部实现、应用场景和内存泄漏

相关文章: