深入理解ThreadLocal及如何解决内存泄漏

1.首先看下面一段简单的代码:

对于初学ThreadLocal的小伙伴来说由于我线程1睡眠了2s,线程2睡眠了1s,按理来说是线程2先运行,应该将值设置进了ThreadLocal中,于是这段代码的运行结果会是:test,但是真正的结果会是这样吗?不妨我们运行来看看:



显然结果出乎我们预料,第一个线程输出的值为null,意思就是没有值。这是为什么呢?
下面我们来看看ThreadLocal的底层实现获取就能明白了。


(1).首先我们点开ThrealLocal这个类,找到里面的一个set方法,即就是往ThreadLocal中设置值的这个方法:(如下)

显然我们能够发现set方法的底层实现:

注意: 这里通过 map.set(this,value);这个方法来设置值的,但是这里有个关键的是这里set的这个key是this,也就是当前对象,而这个当前对象就是ThreadLocal这个需要注意,因为很多人都认为这里设置的这个key是当前线程的id。

第一步:是先去获取到当前线程t。

第二步:是根据这个线程t获取到一个map,那么这个map到底是哪里来的呢?我们跟一下源码自然清晰:
 

于是我们点开这个getMap()这个方法:


 


显然我们追踪这个getMap()方法的源码发现在每个Thread中也就是每个线程中都有一个map,当你每创建一个线程的时候,每个线程就会自动创建一个map,而在最初的那个set()方法中(见下图)

它是将值存放在这个map集合中的,而这个map集合是每个线程所独有的,这也说明了为什么我的那个demo中即使我线程2先执行并且往Threadlocal中设置了值,但是我线程1依然获取的是null。到这里 我们的第一个问题就得以清楚了。


那么接下来我们看看ThreadLocal是如何解决内存泄漏的:

1.首先要理解ThreadLocal是如何解决内存泄漏的那么 我们则需要明白一个知识点那就是java中的弱引用。

java中的引用可分为:强引用,软引用,弱引用,虚引用。

在这里由于需要用到弱引用,所以我在这里简单谈一谈弱引用就行,若想深入理解java中的几种引用那么我推荐大家可以去读一读:周志明写的《深入理解java虚拟机》再搭配上尚硅谷康师傅讲的jvm课程。

什么 是弱引用?简单的讲就是很弱的引用指向某个实例,大家都知道在java中我们创建对象的时候一般都会在堆中开辟一块空间存放实例,然后会有引用指向这个实例。

下面画一下弱引用的内存图(仅供理解使用):

如图,t指向teacher();是虚线,而正常的情况下(也就是强引用)应该是实线。

 这里我们 为了好比较二者的关系就用实现和虚线区别。那么二者到底有何区别了?在java虚拟机中二者最大的区别就是在面对垃圾回收的时候:

对于强引用来说:无论任何情况下,只要强引用关系还在,垃圾回收器就永远不会收集被引用的对象。

对于弱引用来说:当垃圾收集器开始工作的时候,无论你当前内存是否够用,都会回收掉只被弱引用关联的对象,就好比上图一样,弱引用的指向就好比一条虚线(可有可无),当gc时就会被直接回收。


有了弱引用的知识那么我们继续来谈ThreadLocal中如何解决内存泄漏的


这里我们就得接着上面的源码追踪了,这里我们点开set()方法,也就是下图这个set()方法

点开这个set(this,value):

发现他在这里面new了一个Entry,大家都知道,map集合中是可以存放Entry的,于是我们点开这个Entry:


点开这个Entry我们发现这个Entry是WeakReference的一个子类,而这个WeakReference就是我们上面讲的弱引用。而在这个Entry方法中,首先就是调用的super(k),大家都知道super是调用父类的构造方法的然后创建一个对象,在这里就是创建了一个弱引用并且传的这个k就是当前的ThreadLocal对象。什么意思呢?其实就是创建了一个弱引用指向了ThreadLocal实例。下面

画个图帮助理解:

所以说这里为什么要用弱引用?为什么会产生内存泄漏?

假如这里不用弱引用而改用强引用,那么会产生什么样的后果?当

ThreadLocal threadLocal=new ThreadLocal();这行代码不需要的时候,也就是当threadlocal到TheradLocal();引用被断开的时候那么此时的情况如下图:

此时ThreadLcoa();该实例的引用被断开,已经没有用处了。那么根据jvm中根可达性算法可知该实例应该被回收,但是由于我们这里使用的是强引用,就相当于依然还是有引用指向了它但是该实例已经没有任何用处了,垃圾收集器想回收也没办法回收,那么此时就产生了内存泄漏的情况。

那么ThreadLocal底层是如何解决内存泄漏的这种情况呢?那就是使用弱引用,见下图:

此时ThreadLcoa();该实例的引用被断开,已经没有用处了。那么根据jvm中根可达性算法可知该实例应该被回收,但是这里我们使用的是弱引用指向,当jvm触发垃圾回收的时候,即使有弱引用的指向该实例也会被回收,那么ThreadLocal的内存泄露的问题也就解决了。

 

 

 

 

 

 

 

 

 


版权声明:本文为weixin_55568616原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。