还记得第一次接触到ThreadLocal可能导致内存泄露的问题是有一次面试的时候被问到了ThreadLocal的缺陷是什么。当然由于后来没有面试官的联系方式很遗憾也一直没能确认所谓的缺陷是不是就是可能导致内存泄漏,不过后来发现虽然当时弄明白了可是过段时间又搞忘记了这个问题,所以特别记录下来做个备忘吧。
ThreadLocal从名字上来说就很好理解,就是用于线程(Thread)私有(Local)的存储结构,这种结构能够使得线程能够使用只有自己能够访问和修改的变量,从而实现多个线程之间的资源互相隔离,达到安全并发的目的。
也因此,ThreadLocal作为线程并发中的一种资源使用方式,得到了很广泛的应用,比如Spring MVC、Hibernate等。 不过值得一提的是,通常有人会讲ThreadLocal和synchronised等放在一起,作为形成安全并发的手段之一。其实我觉得这是比较容易使人误导的,因为两者的目的性完全不一样。 ThreadLocal主要的是用于独享自己的变量,避免一些资源的争夺,从而实现了空间换时间的思想。 而synchronised则主要用于临界(冲突)资源的分配,从而能够实现线程间信息同步,公共资源共享等,所以严格来说synchronised其实是能够实现ThreadLocal所需要的达到的效果的,只不过这样会带来资源争夺导致并发性能下降,而且还有synchronised、线程切换等一些可能不必要的开销。
对于ThreadLocal而言,其实使用起来有点像基础类型的装箱类型的感觉(个人觉得其实也可以算是一种装饰器模式的使用?),具体的使用就不在啰嗦了。下面就看看这次备忘的重点,如何导致内存泄漏的。
[ThreadLocal可能引起的内存泄露](http://www.cnblogs.com/onlywujun/p/3524675.html)
所以简单的说,主要原因就是在于TreadLocal中用到的自己定义的Map(和常用的Map接口不同)中,使用的Key值是一个WeakReference类型的值(弱引用会在下一次GC时马上释放而不管是否被引用)。那么如果这个Key在GC时被释放了,就会导致Value永远都不会被调用到,但是如果线程不结束,又一直存在。
因为可能不熟悉这部分内容的同学(例如几周以后的我)会感觉有点迷糊为什么这个图是这样的,就具体再解释一下细节点:
接着不得不说的就是我们的大佬Thread类,里面关于ThreadLocal部分的内容主要是这样滴。我们可以看到这里主要是声明了ThreadLocal里面的Map作为类变量来提供给线程使用的。也正式因为如此,才会在ThreadLocal里面的getMap方法是拉取的Thread里面的Map。 p.s. 感觉确实有点绕
所以到这里其实有两个问题是暂时还没想通的,也希望有各位大佬指点一二:
回归到内存泄露是因为WeakReference Key的问题,当然,Java的各位大佬肯定早就想到这个问题了,可以看到人家注释里面是这么说的,大意就是如果key==null的时候,就可以认为这个值无效了,可以调用expunged进行清理:
而这个expungeStaleEntry方法在get、set时都会有间接的调用,而且remove方法中也会显示的调用,这也就是为什么有的文章中说通过在线程调用完成之后,通过调用remove方法能有效的杜绝该泄露问题的原因。
当然简单来说理解到这里就基本明了内存泄露的原因,但是其实再深入一点来说,如果泄露的原因是Key被释放,而Value没有释放,那么是否一定会有泄露呢? 答案当然是否定的,因为如果是一般的线程场景中,除了会调用expungeStaleEntry来进行清理,最差,在线程结束之时,自然也就消除了引用从而使得Value得以GC回收。
所以,会不会有线程一直不结束的场景呢? 当然答案是肯定的,最简单来说线程只要一直在wait就不会结束了,不过这种场景下其实和泄露也没啥关系的感觉。 其实最常用的线程一直不结束的场景,自然就是线程池了。因为这种情况下,线程是一直在不断的重复运行的,从而也就造成了value可能造成累积的情况。