thread|面试官都爱问这个:ThreadLocal与内存泄漏

thread|面试官都爱问这个:ThreadLocal与内存泄漏

文章图片

thread|面试官都爱问这个:ThreadLocal与内存泄漏

文章图片

thread|面试官都爱问这个:ThreadLocal与内存泄漏

文章图片

thread|面试官都爱问这个:ThreadLocal与内存泄漏

文章图片

thread|面试官都爱问这个:ThreadLocal与内存泄漏

文章图片

thread|面试官都爱问这个:ThreadLocal与内存泄漏

文章图片

thread|面试官都爱问这个:ThreadLocal与内存泄漏

文章图片

thread|面试官都爱问这个:ThreadLocal与内存泄漏

文章图片



ThreadLocal是面试中经常问到的点 , 今天我们来讲解下ThreadLocal 。
1、什么是ThreadLocalThread类有一个成员变量threadLocals:

我们发现Thread类中并没有提供成员变量threadLocals的设置与访问的方法 , 那么每个线程的实例threadLocals参数我们如何操作呢?这时我们的主角:ThreadLocal就登场了 。
ThreadLocal是线程Thread中属性threadLocals的管理者 。 也就是说我们对于ThreadLocal的get set , remove的操作结果都是针对当前线程Thread实例的threadLocals存 , 取 , 删除操作 。
ThreadLocal提供了线程独有的局部变量 , 可以在整个线程存活的过程中随时取用 , 极大地方便了一些逻辑的实现 。 常见的ThreadLocal用法有:
1)存储单个线程上下文信息 。 比如存储id等;
2)减少参数传递 。 比如做一个trace工具 , 能够输出工程从开始到结束的整个一次处理过程中所有的信息 , 从而方便debug 。 由于需要在工程各处随时取用 , 可放入ThreadLocal 。
2、ThreadLocal使用实例为了更直观的体会ThreadLocal的使用我们假设如下场景:
1)我们给每个线程生成一个ID
2)一旦设置 , 线程生命周期内不可变化
3)容器活动期间不可以生成重复的ID

3、ThreadLocal源码解析ThreadLocal类结构及方法解析:

ThreadLocal三个方法get setremove以及内部类ThreadLocalMap、Entry 。
我们以get方法为例:

其中getMap(t)返回的是当前线程的threadLocals , 如下图 , 然后根据当前ThreadLocal实例对象作为key获取ThreadLocalMap中的value , 如果首次进来这调用setInitialValue() 。


getEntry方法:当前ThreadLocal实例对象作为key , key的hashCode与table数组的长度做位与运算 , 得出table数组的索引下标 , 取出对应的Entry 。

Entry类是ThreadLocalMap的静态内部类 , 继承了WeakReference , 从它的构造方法可以看出 , ThreadLocal k是一个弱引用 , 指向了当前ThreadLocal实例 。

set方法类似 , 不再赘述 。

梳理完源代码后 , 可以得到这样一个关系图:

4、ThreadLocal导致的内存泄漏什么叫内存泄漏 , 对应的什么叫内存溢出?
① Memory overflow:内存溢出 , 没有足够的内存提供申请者使用 。
② Memory leak:内存泄漏 , 程序申请内存后 , 无法释放已申请的内存空间 , 内存泄漏的堆积终将导致内存溢出 。
显然是TreadLocal在不规范使用的情况下导致了内存没有释放 。

Entry继承了WeakReference类 , 我们来回顾下引用的知识:

既然WeakReference在下一次gc即将被回收 , 那么我们的程序为什么没有出问题呢?我们测试下弱引用的回收机制:

输出结果:hello world , 没有被回收 , 这是因为这个String对象有一个强引用和一个弱引用同时指向了它 。
我们改下代码:

输出结果为:weakReference已经被GC回收 , 只有一个弱引用被回收了 。
上面演示了弱引用的回收情况 , 下面我们看下ThreadLocal的弱引用回收情况 。

如上图所示 , 我们在作为key的ThreadLocal对象没有外部强引用 , 下一次gc必将产生key值为null的数据 , 若线程没有及时结束必然出现 , 一条强引用链
Threadref->Thread->ThreadLocalMap->Entry-> value , 导致value对应的Object一直无法被回收 , 产生内存泄露 。
查看源码我们可以看到 , ThreadLocal的get、set和remove方法都实现了对所有key为null的value的清除 , 但仍可能会发生内存泄露 , 因为可能使用了ThreadLocal的get或set方法后发生GC , 此后不调用get、set或remove方法 , 为null的value就不会被清除 。
5、总结使用ThreadLocal时会发生内存泄漏的前提条件:
① ThreadLocal引用被设置为null , 且后面没有set , get , remove操作
② 线程一直运行 , 不停止 。 (线程池)
③ 触发了垃圾回收 。 (Minor GC或Full GC)
我们看到ThreadLocal出现内存泄漏条件还是很苛刻的 , 所以我们只要破坏其中一个条件就可以避免内存泄漏 , 但为了更好的避免这种情况的发生我们使用ThreadLocal时遵守以下两个原则:
① ThreadLocal申明为private static final 。 Private与final 尽可能不让他人修改变更引用 ,Static 表示为类属性 , 只有在程序结束才会被回收 。
② ThreadLocal使用后务必调用remove方法 , 最简单有效的方法是使用后将其移除 。
【thread|面试官都爱问这个:ThreadLocal与内存泄漏】欢迎小伙伴们留言交流~


    推荐阅读