Java ThreadLocal使用、原理、问题分析
2020-07-30 17:44 宋海宾 阅读(250) 评论(0) 收藏 举报1. 概述
ThreadLocal叫做线程本地变量,也叫做线程本地存储。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。
ThreadLocal类主要有四个方法,分别是:
1)ThreadLocal.get:用来获取ThreadLocal在当前线程中保存的变量副本
2)ThreadLocal.set:用来设置ThreadLocal在当前线程中变量的副本
3)ThreadLocal.remove:用来删除ThreadLocal在当前线程中变量的副本
4)ThreadLocal.initialValue:是一个protected方法,一般是用来在使用时进行重写的。在调用get()方法时,如果ThreadLocal没有被当前线程赋值或当前线程刚调用remove方法,就返回此方法值。
2.用法
public class ThreadLocalMyDemo {
3.原理
每个线程中存在一个ThreadLocalMap 表,获取当前线程的 ThreadLocalMap入参是当前线程实例。
例如:
if (map != null)
ThreadLocal中hash冲突的解决方法:
和HashMap的最大的不同在于,ThreadLocalMap结构非常简单,没有next引用,也就是说ThreadLocalMap中解决Hash冲突的方式并非链表的方式,而是采用线性探测的方式,所谓线性探测,就是根据初始key的hashcode值确定元素在table数组中的位置,如果发现这个位置上已经有其他key值的元素被占用,则利用固定的算法寻找一定步长的下个位置,依次判断,直至找到能够存放的位置。
ThreadLocalMap解决Hash冲突的方法就是简单的步长加1,寻找下一个相邻的位置。
在每个线程Thread内部有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这个threadLocals就是用来存储实际的变量副本的,键值为当前ThreadLocal变量,value为变量副本(即T类型的变量)。
初始时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为value,存到threadLocals。
然后在当前线程里面,如果要使用副本变量,就可以通过get方法在threadLocals里面查找。
4.内存泄漏
ThreadLocal 本身并不存储值,它只是作为一个 key保存到ThreadLocalMap中,但是这里要注意的是它作为一个key用的是弱引用,因为没有强引用链,弱引用在GC的时候可能会被回收。这样就会在ThreadLocalMap中存在一些key为null的键值对(Entry)。因为key变成null了,我们是没法访问这些Entry的,但是这些Entry本身是不会被清除的,为什么呢?因为存在一条强引用链。即线程本身->ThreadLocalMap->Entry也就是说,恰恰我们在使用线程池的时候,线程使用完了是会放回到线程池循环使用的。由于ThreadLocalMap的生命周期和线程一样长,如果没有手动删除对应key就会导致这块内存即不会回收也无法访问,也就是内存泄漏。
其实,ThreadLocalMap的设计中已经考虑到这种情况,也加上了一些防护措施:在ThreadLocal的get(),set(),remove()的时候都会清除线程ThreadLocalMap里所有key为null的value。但是这些举动不能保证内存就一定会回收,因为可能这条线程被放回到线程池里后再也没有使用,或者使用的时候没有调用其get(),set(),remove()方法。
内存泄漏归根结底是由于ThreadLocalMap的生命周期跟Thread一样长
如何避免内存泄漏
调用ThreadLocal的get()、set()方法后再调用remove方法,将Entry节点和Map的引用关系移除,这样整个Entry对象在GC Roots分析后就变成不可达了,下次GC的时候就可以被回收。
如果使用ThreadLocal的set方法之后,没有显示的调用remove方法,就有可能发生内存泄露,所以养成良好的编程习惯十分重要,使用完ThreadLocal之后,记得调用remove方法。
在不使用线程池的前提下,即使不调用remove方法,线程的"变量副本"也会被gc回收,即不会造成内存泄漏的情况。
浙公网安备 33010602011771号