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

相关推荐
徐*红12 分钟前
java 线程池
java·开发语言
尚学教辅学习资料12 分钟前
基于SSM的养老院管理系统+LW示例参考
java·开发语言·java毕设·养老院
2401_8576363912 分钟前
计算机课程管理平台:Spring Boot与工程认证的结合
java·spring boot·后端
1 9 J14 分钟前
Java 上机实践4(类与对象)
java·开发语言·算法
Code apprenticeship15 分钟前
Java面试题(2)
java·开发语言
也无晴也无风雨1 小时前
深入剖析输入URL按下回车,浏览器做了什么
前端·后端·计算机网络
憨子周1 小时前
2M的带宽怎么怎么设置tcp滑动窗口以及连接池
java·网络·网络协议·tcp/ip
霖雨3 小时前
使用Visual Studio Code 快速新建Net项目
java·ide·windows·vscode·编辑器
SRY122404193 小时前
javaSE面试题
java·开发语言·面试
Fiercezm3 小时前
JUC学习
java