来说说ThreadLocal内存溢出问题

本文解析ThreadLocal内存管理机制,讲解其在多线程中的作用,演示使用示例,讨论溢出风险及解决方案,涵盖原理、实例和内存泄露防范。

前言

上次有个小伙伴问我,说他面试的时候,被问到ThreadLocal内存溢出问题,没有回答出来;那我们今天就来了解一下ThreadLocal。

ThreadLocal介绍

多线程在访问同一个变量时会产生线程安全问题,ThreadLocal是JDK包提供的,它提供线程本地变量,如果创建一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个副本,在实际多线程操作的时候,操作的是自己本地内存中的变量,从而规避了线程安全问题,如下图所示

ThreadLocal使用

开启两个线程,在每个线程内部设置了本地变量的值,然后调用print方法打印当前本地变量的值。如果在打印之后调用本地变量的remove方法会删除本地内存中的变量,代码如下所示

执行结果



thread1 :localVar1 thread2 :localVar2 thread1 after remove : null thread2 after remove : null

上面代码能够看出,虽然2个线程都使用了ThreadLocal localVar变量,但是线程里面的变量互相不影响。

ThreadLocal原理

我们来看看ThreadLocal是怎么实现的?我们来看一下源码

set源码

添加图片注释,不超过 140 字(可选)

 在上面的代码中,(2)处调用getMap方法获得当前线程对应的threadLocals,该方法代码如下



ThreadLocalMap getMap(Thread t) { return t.threadLocals; //获取线程自己的变量threadLocals,并绑定到当前调用线程的成员变量threadLocals上 }

如果调用getMap方法返回值不为null,就直接将value值设置到threadLocals中(key为当前ThreadLocal引用,值为本地变量);如果getMap方法返回null说明是第一次调用set方法(前面说到过,threadLocals默认值为null,只有调用set方法的时候才会创建map),这个时候就需要调用createMap方法创建threadLocals,该方法如下所示



void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }

createMap方法不仅创建了threadLocals,同时也将要添加的本地变量值添加到了threadLocals中。

get源码

在get方法的实现中,首先获取当前调用者线程,如果当前线程的threadLocals不为null,就直接返回当前线程绑定的本地变量值,否则执行setInitialValue方法初始化threadLocals变量。在setInitialValue方法中,类似于set方法的实现,都是判断当前线程的threadLocals变量是否为null,是则添加本地变量(这个时候由于是初始化,所以添加的值为null),否则创建threadLocals变量,同样添加的值为null

remove实现

remove方法判断该当前线程对应的threadLocals变量是否为null,不为null就直接删除当前线程中指定的threadLocals变量

溢出分析

每个线程内部有一个名为threadLocals的成员变量,该变量的类型为 ThreadLocal.ThreadLocalMap类型(类似于一个HashMap),其中的key为当前定义的ThreadLocal变量的this引用,value为我们使用set方法设置的值。每个线程的本地变量存放在自己的本地内存变量threadLocals中,如果当前线程一直不消亡,那么这些本地变量就会一直存在(所以可能会导致内存溢出),因此使用完毕需要将其remove掉。

看下图:

番外篇

1、ThreadLocalMap 内部 Entry 中 key 使用的是对 ThreadLocal 对象的弱引用,这是为了避免内存泄露,因为如果是强引用,那么即使其他地方没有对 ThreadLocal 对象的引用,ThreadLocalMap 中的 ThreadLocal 对象还是不会被回收,而如果是弱引用则这时候 ThreadLocal引用是会被回收掉的。

2、但是对于的 value 还是不能被回收,这时候 ThreadLocalMap 里面就会存在 key 为 null 但是 value 不为 null 的 entry 项,虽然 ThreadLocalMap 提供了 set,get,remove 方法在一些时机下会对这些 Entry 项进行清理,但是这是不及时的,也不是每次都会执行的,所以一些情况下还是会发生内存泄露,所以在使用完毕后即使调用 remove 方法才是解决内存泄露的最好办法。

3.线程池里面设置了 ThreadLocal 变量一定要记得及时清理,因为线程池里面的核心线程是一直存在的,如果不清理,那么线程池的核心线程的 threadLocals 变量一直会持有 ThreadLocal 变量。

总结

希望这篇文章能够帮助小伙伴们理解,这里再提供网上常用的图片,有助于小伙伴理解

谢谢!!!

ThreadLocal内存溢出的主要原因与其工作方式及使用习惯有关。在Java中,ThreadLocal通过维护一个以Thread为键,以用户设置的值为值的映射来工作,该映射存储在每个Thread对象的ThreadLocal.ThreadLocalMap字段里,每个线程都有自身的线程局部变量副本,不同线程间的这些变量互不干扰[^2]。 当线程结束时,如果ThreadLocal变量没有被手动清除,就会导致这部分内存无法被回收,最终造成内存泄漏。而且ThreadLocal变量会一直存在于ThreadLocalMap中,这也是引发内存问题的因素之一[^1]。另外,在极端情况下,只创建ThreadLocal但不调用set、get、remove方法等,也可能导致内存泄漏问题的出现[^3]。 解决ThreadLocal内存溢出问题,最有效的办法是在使用完ThreadLocal后手动调用remove()方法。remove()方法会获取当前线程的threadLocals(ThreadLocalMap)并调用其remove()方法,将Map中对应的key值设置为null,断开引用,以此来避免内存泄漏[^3][^4][^5]。不过,即便采取这样的措施,也只能尽可能避免内存泄漏,不能完全杜绝,因为极端情况下仍可能出现问题[^3]。 以下是手动调用 remove() 方法的示例代码: ```java public class ThreadLocalExample { private static final ThreadLocal<Integer> threadLocal = new ThreadLocal<>(); public static void main(String[] args) { try { // 设置值 threadLocal.set(10); // 使用 threadLocal Integer value = threadLocal.get(); System.out.println("ThreadLocal value: " + value); } finally { // 手动调用 remove 方法 threadLocal.remove(); } } } ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值