为什么我学了那么久还是对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的内容也会被清空。多一层保障!

相关推荐
我命由我123452 分钟前
Android 开发 Room 数据库升级问题:A migration from 6 to 7 was required but not found.
android·java·java-ee·android studio·android jetpack·android-studio·android runtime
kiss strong4 分钟前
springboot替换word模板&加入二维码&加水印&转为pdf
spring boot·后端·pdf
黄筱筱筱筱筱筱筱4 分钟前
7.适合新手小白学习Python的异常处理(Exception)
java·前端·数据库·python
Stecurry_306 分钟前
Springboot整合SpringMVC --从0到1
java·spring boot·后端
Serene_Dream6 分钟前
NIO 的底层机理
java·jvm·nio·mmap
skywalker_1111 分钟前
多线程&JUC
java·开发语言·jvm·线程池
黎雁·泠崖12 分钟前
Java基础核心能力总结:从语法到API的完整知识体系
java·开发语言
JaguarJack17 分钟前
PHP 现在可以零成本构建原生 iOS 和 Android 应用 NativePHP for Mobile v3 发布
后端·php·laravel·服务端
_周游19 分钟前
Java8 API 文档搜索引擎_2.索引模块(实现细节)
java·搜索引擎·intellij-idea
鱼跃鹰飞19 分钟前
大厂面试真题-说说Kafka消息的不重复和不丢失
java·分布式·kafka