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

相关推荐
IT_陈寒17 分钟前
Redis性能翻倍秘籍:10个99%开发者不知道的冷门配置优化技巧
前端·人工智能·后端
LB211220 分钟前
Redis 黑马skyout
java·数据库·redis
洛小豆25 分钟前
Swagger3学习与实践指南
spring boot·后端·spring cloud
豐儀麟阁贵27 分钟前
Java知识点储备
java·开发语言
hrrrrb33 分钟前
【Spring Security】Spring Security 密码编辑器
java·hive·spring
Victor35634 分钟前
Redis(58)如何配置和查看Redis的慢查询日志?
后端
Victor35636 分钟前
Redis(59)Redis的主从复制是如何实现的?
后端
豐儀麟阁贵36 分钟前
2.3变量与常量
java·开发语言
摇滚侠2 小时前
Spring Boot 3零基础教程,自动配置机制,笔记07
spring boot·笔记·后端
兮动人2 小时前
Eureka注册中心通用写法和配置
java·云原生·eureka