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

相关推荐
努力的小郑2 分钟前
Canal 不难,难的是用好:从接入到治理
后端·mysql·性能优化
赫瑞21 分钟前
数据结构中的排列组合 —— Java实现
java·开发语言·数据结构
Victor3561 小时前
MongoDB(87)如何使用GridFS?
后端
Victor3561 小时前
MongoDB(88)如何进行数据迁移?
后端
小红的布丁1 小时前
单线程 Redis 的高性能之道
redis·后端
GetcharZp1 小时前
Go 语言只能写后端?这款 2D 游戏引擎刷新你的认知!
后端
周末也要写八哥2 小时前
多进程和多线程的特点和区别
java·开发语言·jvm
惜茶2 小时前
vue+SpringBoot(前后端交互)
java·vue.js·spring boot
宁瑶琴3 小时前
COBOL语言的云计算
开发语言·后端·golang
杰克尼3 小时前
springCloud_day07(MQ高级)
java·spring·spring cloud