Threadlocal中的弱引用到底是怎么一回事
最近复习面试资料时偶尔发现有观点指出Threadlocal实现的一个潜在问题是内存泄漏现象。进一步阅读他的博客后发现他详细解释了这一问题的核心原因:即在Entry处的key变量存在对ThreadLocal实例对象的弱引用关系,在经过一次GC回收后由于键值可能仍保留在内存中而使得最终出现资源浪费的情况。
我反复思考后仍感到困惑——这个问题早就是有预谋地使用弱引用所致。。但是到这里解释起来可能会让人一头雾水:那么现在我们来理清思路重新阐述一下吧
什么是Threadlocal
ThreadLocal 是 JDK java.lang 包下的一个类,是天然的线程安全的类
在源码中可以看到:该类实现了为每个线程分配专用存储空间的能力。这些本地变量与全局变量或普通属性体之间存在显著差异。当任何线程访问该实例时(通过调用get或set方法),都会获得一个独立且专属于自身的拷贝进行初始化操作。通常情况下,在类定义中定义为私有静态字段时就会采用此机制以实现对状态信息(如用户标识符或事务标记)与其对应的线程进行绑定存储的目的。
Threadlocal的原理实现
我想说的是:然而他仍然提供了一个Thread局部变量机制。但个人而言,在面对他的设计时我依然认为这种机制存在明显的漏洞(特别是考虑到弱引用导致的数据持久性问题)。通过该机制实现的数据存储功能时有时效性的限制,并且其复杂的嵌套结构设计使得整体架构更加难以维护与理解)。该机制通过将数据存储在 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;
}
java

可以看出他在设置与获取线程内的数据上采取了特别的操作。请注意,在之前的描述中存在误述,请更正为:他实际操作的是桥梁而非小偷(笑)。


上面就是我从其他博主那里爬过来的流程图(忘记具体地址了!sorry~)
回归正题
说到这里朋友们是不是能有一点理解我一开始提出的问题了呀~
实际上 ThreadLocal 不存储任何数据 其中数据实际上是存储在线程实例中 从实际运行情况来看属于线程内存泄漏 但在底层运行机制上是 Thread 对象成员变量 threadLocals 持有大量 K-V 结构 并且由于线程一直处于活跃状态 导致变量 threadLocals 无法释放而被回收 同时一般情况下 一个应用不会定义大量 ThreadLocal 类实例 因此常见的泄漏源是线程始终处于活跃状态 导致 threadLocals 无法及时回收 然而我们知道 在 ThreadLocalMap 中 Entry 的 Key 使用了弱引用 WeakReference<ThreadLocal<?>> 当没有强引用来引用 ThreadLocal 实例时 JVM 的垃圾回收机制会自动回收这些 Key 这样在 ThreadLocalMap 中就会出现一些 Key 为 null 但 Value 不为 null 的 Entry 结构 这些Entry项如果没有主动清理 就会一直停留在 Map 中 这也是为什么在 get() set() 和 remove() 方法中都需要包含清理 Map 中 Key 为 null 的代码块
上面是不是你们觉得的缺点?漏~
ThreadLocal中的一个重要特点在于其Entry结构的Key采用了弱引用机制。设想如果将此Key字段改为使用强引用,则会导致 ThreadLocalMap中的所有数据与Thread对象的生命周期紧密绑定在一起。这将使得当大量线程长时间保持活跃时容易出现内存泄漏问题。而采用弱引用的方式,在JVM触发 garbage collection(垃圾回收)时会自动处理这些 weak references。这样,在后续调用get()、set()或remove()方法时就会自动清除 ThreadLocalMap中 Key字段为 null 的条目,从而实现了惰性删除以释放内存资源的作用。
其次就是在ThreadLocalMap中存在相应的遍历操作来查找key为null的槽位对象并从而实现槽位对象的删除哦~
总结来说,我认为这不是一个缺点 反而是非常灵活的一个点~因为它允许我们在需要时将weak references into strong ones(通过使用static关键字)
