最近在复习面试资料的时候,偶然间看到了有人觉得Threadlocal的一个缺点是内存泄漏,顺着他的博客往下看,他就是觉得在entry中的key存在着对threadlocal实例对象的弱引用,然后就觉得在一次gc以后会因为key为null但是value还有值从而造成内存泄漏。
我左思右想,还是觉得不对劲,再查看相关文献和源码的时候才恍然大悟——人家早就是有预谋的使用弱引用好吗。。。可是说到这里有人会说这个人到底在讲什么??那么现在我们理顺思路重新讲呗
ThreadLocal 是 JDK java.lang 包下的一个类,是天然的线程安全的类
在源码中是这样说的:ThreadLocal提供线程局部变量。这些变量与正常的变量不同,因为每一个线程在访问ThreadLocal实例的时候(通过其get或set方法)都有自己的、独立初始化的变量副本。ThreadLocal实例通常是类中的私有静态字段,使用它的目的是希望将状态(例如,用户ID或事务ID)与线程关联起来。
这里我先说一下,虽然他提供线程局部变量,但我还是觉得他就是一个对Thread类中一些数据的小偷(因为弱引用偷完就消失不见了hhhh)。它会将数据存放到Thread对象的ThreadlocalMap的entry中(有一点套娃的感觉,都是内部类套内部类)
这里简单放一下他的源码实现,引出下面的set和get。
//获取下一个ThreadLocal实例的哈希数
private final int threadLocalHashCode = nextHashCode();
//原子计数器,主要到它被定义为静态
private static AtomicInteger nextHashCode =
new AtomicInteger();
//哈希数(增长数),也是带符号的32位整型值黄金分割值的取正
private static final int HASH_INCREMENT = 0x61c88647;
//生成下一个哈希数
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
//threadLocals属性不为null则覆盖key为当前的ThreadLocal实例,值为value
map.set(this, value);
} else {
//threadLocals属性为null,则创建ThreadLocalMap,第一个项的Key为当前的ThreadLocal实例,值为value
createMap(t, value);
}
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
可以看出来他的set和get都是操作的Thread中的数据(这里更正一下,应该是桥梁而不是小偷hhh)
上面就是我从其他博主那里爬过来的流程图(忘记具体地址了!sorry~)
说到这里朋友们是不是能有一点理解我一开始提出的问题了呀~
其实ThreadLocal本身不存放任何的数据,而ThreadLocal中的数据实际上是存放在线程实例中,从实际来看是线程内存泄漏,底层来看是Thread对象中的成员变量threadLocals持有大量的K-V结构,并且线程一直处于活跃状态导致变量threadLocals无法释放被回收。threadLocals持有大量的K-V结构这一点的前提是要存在大量的ThreadLocal实例的定义,一般来说,一个应用不可能定义大量的ThreadLocal,所以一般的泄漏源是线程一直处于活跃状态导致变量threadLocals无法释放被回收。但是我们知道,ThreadLocalMap中的Entry结构的Key用到了弱引用(·WeakReference
上面是不是你们觉得的缺点?漏~
ThreadLocal中一个设计亮点是ThreadLocalMap中的Entry结构的Key用到了弱引用。试想如果使用强引用,等于ThreadLocalMap中的所有数据都是与Thread的生命周期绑定,这样很容易出现因为大量线程持续活跃导致的内存泄漏。使用了弱引用的话,JVM触发GC回收弱引用后,ThreadLocal在下一次调用get()、set()、remove()方法就可以删除那些ThreadLocalMap中Key为null的值,起到了惰性删除释放内存的作用。
其次就是在ThreadLocalMap会存在相应的遍历来查看key为null的槽位并且进行删除哦~
综上所述,我并不觉得这是一个缺陷,反而是一个很灵活的点~因为我们可以手动将弱引用变成强引用嘛(static关键字了解一下~)