ThreadLocal
中填充的的是当前线程的变量,该变量对其他线程而言是封闭且隔离的,ThreadLocal
为变量在每个线程中创建了一个副本,这样每个线程都可以访问自己内部的副本变量。其有如下特点:
Session
会话管理。ThreadLocalMap
为 ThreadLocal
的一个静态内部类,里面定义了Entry
来保存数据。而且是继承的弱引用。在Entry
内部使用ThreadLocal
作为key
,使用我们设置的value
作为value
。
对于每个线程内部有个ThreadLocal.ThreadLocalMap
变量,存取值的时候,也是从这个容器中来获取。
//threadlocal存入数据方法
public void set(T value){
//获取当前线程对象
Thread t=Thread.currentThread();
//将当前线程作为入参 得到ThreadLocalMap (类似Map)
ThreadLocalMap map =getMap(t);
if(map!=null){
//map不为空写入this :ThreadLocal对象
map.set(this,value);
}else{
//map为空,先创建再写入
createMap(t,value);
}
}
这样设计的好处是,如果这个变量不再被其他对象使用时,可以自动回收这个ThreadLocal对象,以此避免可能的内存泄露。
如果key设为强引用,假如JVM中存在大量线程都在运行,导致JVM中存在大量ThreadLocal对象无法被回收(内存泄漏-GC 无法清理这块内存区域)。大量内存泄漏会导致内存溢出。
Key对应ThreadLocal只要发生GC就会被回收,Key变为null,Entry中Value无法被访问,随着类似线程不断存入数据,Value是不可访问对象会导致内存泄漏最终导致OOM。
使用了线程池,可以达到“线程复用”的效果。但是归还线程之前记得清除ThreadLocalMap
,要不然再取出该线程的时候,ThreadLocal
变量还会存在。这就不仅仅是内存泄露的问题了,整个业务逻辑都可能会出错。
写入ThreadLocal中数据用完以后一定要立即手动清除。
要防止 ThreadLocal 内存泄漏,可以考虑以下方法:
使用完 ThreadLocal 后及时调用 remove() 方法:在不再需要使用 ThreadLocal 存储的数据时,手动调用 ThreadLocal.remove() 方法将该数据从当前线程的 ThreadLocalMap 中清除。这样可以确保 ThreadLocalMap 不会持有对对象的引用,从而帮助垃圾回收器正常回收不再需要的对象。
javaCopy code
ThreadLocal<Object> threadLocal = new ThreadLocal<>();
// 存储数据
threadLocal.set(someData);
// 使用完毕后清除
threadLocal.remove();
使用 try-with-resources 或 try-finally 块:如果你的 ThreadLocal 变量在需要清理的资源管理上下文中使用,可以使用 try-with-resources(自动清理)或 try-finally(手动清理)块来确保及时清理。
javaCopy code
try (ThreadLocalResource resource = new ThreadLocalResource()) {
// 使用 ThreadLocalResource
}
// 或者使用 try-finally
ThreadLocalResource resource = new ThreadLocalResource();
try {
// 使用 ThreadLocalResource
} finally {
resource.close(); // 在 close 方法中清理 ThreadLocal 变量
}
使用 InheritableThreadLocal:如果需要在子线程中访问父线程的 ThreadLocal 变量,并且确保在子线程中正确清理,可以考虑使用 InheritableThreadLocal。这个类允许子线程继承父线程的 ThreadLocal 变量,并在子线程完成后自动清理。
javaCopy code
ThreadLocal<String> threadLocal = new InheritableThreadLocal<>();
threadLocal.set("Hello, Parent Thread");
Runnable childTask = () -> {
String value = threadLocal.get(); // 子线程可以访问父线程的 ThreadLocal 变量
// ...
};
Thread childThread = new Thread(childTask);
childThread.start();
通过采取这些预防措施,可以有效避免 ThreadLocal 变量的内存泄漏问题,确保不再需要的对象能够被及时回收。