为什么我学了那么久还是对ThreadLocal一脸懵逼?

如何理解ThreadLocal


全局容器这个概念相信大家都不陌生了,就是在任何地方都可以访问得到的一个容器

在一个类里面声明static的容器,可用称之为全局容器

arduino 复制代码
public static final Map<String,String> SERVERS_LIST = new ConcurrentHashMap<>(16);

那ThreadLocal怎么理解呢?

我们同样可以把ThreadLocal理解为一个全局容器,它们有一个共同的特点,允许在任何地方从全局容器拿到东西

不同点在哪?ThreadLocal是独属于当前线程的全局容器,而上面讲的Map容器是属于所有线程的全局容器

每个线程本身就有一个ThreadLocalMap 容器,最经典的案例可以看一下SpringSecurityContextHolder容器,其本质就是通过ThreadLocal实现的,具体大家可以往下看

ThreadLocalMap的类结构体


每个Thread维护一个ThreadLocalMap对象,这个Map的key是ThreadLocal实例本身,value是存储的值要隔离的变量

  • 每个Thread线程内部都有一个Map(ThreadLocalMap::threadlocals)

  • Map里面存储ThreadLocal对象(key)和线程的变量副本(value)

  • Thread内部的Map由ThreadLocal维护,由ThreadLocal负责向map获取和设置变量值

  • 对于不同的线程,每次获取副本值时,别的线程不能获取当前线程的副本值,就形成了数据之间的隔离。

    这也是为什么ThreadLocal能够做到线程隔离的原因

如果这张图还是不太清楚,可以再看看下面几张图

ThreadLocal内存泄漏 - 为什么set后要remove?


内存泄漏问题:指程序中动态分配的堆内存由于某种原因没有被释放或者无法释放,造成系统内存的浪费,导致程序运行速度减慢或者系统奔溃等严重后果。内存泄漏堆积将会导致内存溢出。

ThreadLocal的内存泄漏问题一般考虑和Entry对象有关 ,在上面的Entry定义可以看出ThreadLocal::Entry被弱引用所修饰

既然提到强弱引用,难道说ThreadLocal内存泄漏问题就是因为强弱引用导致的吗?其实不然,我们分类来讨论一下

  • 使用强引用

    使用了强引用会发生什么呢?

    当ThreadLocal Ref被回收了,由于在Entry使用的是强引用,在Current Thread还存在的情况下就存在着到达Entry的引用链,

    无法清除掉ThreadLocal的内容,同时Entry的value也同样会被保留

    也就是说,如果使用强引用可能就会出现内存泄漏的问题

  • 使用弱引用

    当ThreadLocal Ref被回收了,由于在Entry使用的是弱引用,因此在下次垃圾回收的时候就会将ThreadLocal对象清除 ,这个时候Entry中的KEY=null 。但是由于ThreadLocalMap中任然存在Current Thread Ref这个强引用,因此Entry中value的值任然无法清除。还是存在内存泄漏的问题。

总结与思考


根据上面强弱引用的讨论,我们可以知道使用ThreadLocal造成内存泄漏跟强弱引用没有关系

造成内存泄漏的真正原因是:ThreadLocalMap的生命周期与Thread一致,如果不手动清除掉Entry对象的话就可能会造成内存泄漏

因此,需要我们在每次使用完后手动的remove掉Entry对象,但是我对此有几个疑问并结合了我自己的思考:

  • 既然ThreadLocalMap和Thread的生命周期是一样的,那在当前线程结束后,ThreadLocalMap不是也会被销毁吗?

    怎么就造成了内存泄漏了呢?线程池中的线程复用有关

    当一个线程执行完毕并被正确地回收时,它内部的ThreadLocalMap实例也会随着线程一同被垃圾回收器回收,因此不会出现内存泄漏的问题。然而,在使用ThreadLocal结合线程池的情况下,可能会遇到内存泄漏的风险

    在线程池中,工作线程通常会复用,而不是每次任务完成后就销毁。这意味着线程会持续存在,即使某个使用了ThreadLocal的任务已经完成了它的生命周期

  • 既然强弱引用都会导致内存泄漏,为什么用的是弱引用而不是强引用呢?

    在ThreadLocalMap的set/getEntry 中,会对key进行判断,如果key为null,那么value也会被设置为null ,这样即使在忘记调用了remove方法,当ThreadLocal被销毁时,对应value的内容也会被清空。多一层保障!

相关推荐
5 分钟前
Golang标准库介绍
后端
悟空868 分钟前
扩展你的RAG系统:自定义处理器与向量化方法
后端
平山11 分钟前
浅析JavaScript的内存机制
javascript·面试
JojO_o16 分钟前
frida 在安卓模拟器上的使用
java
青青奇犽17 分钟前
跨域问题全解析:七种方法轻松拿捏跨域
前端·javascript·面试
Wo3Shi4七20 分钟前
MySQL底层原理(第一期)
数据库·后端·mysql
kill bert21 分钟前
第32周Java微服务入门 微服务基础
java·开发语言·微服务
fliter23 分钟前
性能比拼: Pingora vs Nginx (My NEW Favorite Proxy)
后端
小智疯狂敲代码24 分钟前
Java架构师成长之路-框架源码系列-整体认识Spring体系结构(2)
java
LeoLiz25 分钟前
Spring security原理解析 + 实战
后端