ThreadLocal的使用和注意事项
Posted 信小呆
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ThreadLocal的使用和注意事项相关的知识,希望对你有一定的参考价值。
一、前言
对ThreadLocal不熟悉的同学,可以先参考我的另外一篇文章浅谈ThreadLocal
在讨论内存泄漏之前,需要明白java中的四种引用,同样可以移步到java中的四种引用
什么是内存泄露?
大白话讲,就是我自己创建的对象,在一系列操作后,我访问不到该对象了,我认为它已经被回收掉了,但该对象却一直存在与内存中。
二、示例
先给出一个简单例子,用来说明引用与对象的指向关系
package com.yang.testThreadLocal;
public class Main
private static ThreadLocal<Integer> tl = new ThreadLocal<>();
public static void main(String[] args)
tl.set(1);
Thread t = new Thread(() ->
tl.set(2);
System.out.println("子线程:" + tl.get());
);
t.start();
System.out.println("主线程:" + tl.get());
指向关系如下所示
在ThreadLocalMap中,维护一个Entry类型的数组,Entry是一个(k,v)结构,可以把ThreadLocalMap理解为一个定制化的HashMap。不过Entry的key是对ThreadLocal的一个弱引用,在执行tl=null后,1号线断开,则该ThreadLocal会在下一次GC到来的时候,被回收掉。
为什么这里的key保持着对ThreadLocal的一个弱引用呢?保持强引用行不行?
假设这里的key保持对ThreadLocal的强引用,则当我的程序用不到该ThreadLocal时,我手动执行了tl=null,此时1号线断开,而这里的5号线是实线,5号线没有断开,因此ThreadLocal对象无法被回收掉,一直存在于内存中,造成内存泄露。
看来,这里的弱引用,能够保证用不到的ThreadLocal被回收掉。
弱引用就能完全防止内存泄露了吗?
由上面的分析,弱引用能够防止释放不掉ThreadLocal引起的内存泄露。但是,却不能防止释放不掉Integer引起的内存泄露。首先,执行tl=null,则1号线断开,GC到来时,5号线断开,此时ThreadLocal被回收掉了,这个key被置为了null,可是这个key对应的value强引用着Integer对象,该Integer无法在用户代码中访问到了,但却依然存在于内存中,造成内存泄露。
既然依然存在着内存泄露,那么JDK团队是怎么解决的呢?
其实,ThreadLocal中的get()、set()方法,不是单纯地去做获取、设置的操作。在它们的方法内部,依然会遍历该Entry数组,删除所有key为null的Entry,并将相关的value置为null,从而够解决因释放不掉value而引起的内存泄露。
有这些get()、set()方法,就能完全地防止内存泄漏吗?
但我们手动将tl置为null后,就已经没法调用这些get()、set()方法了。所以,预防内存泄露的最佳实践是,在使用完ThreadLocal后,先调用tl.remove(),再调用tl=null。tl.remove()能够使得ThreadLocalMap删除该ThreadLocal所在的Entry,以及将value置为null,tl=null使得ThreadLocal对象真正地被回收掉。
以上是关于ThreadLocal的使用和注意事项的主要内容,如果未能解决你的问题,请参考以下文章