ThreadLoacl全面解析
ThreadLocal是什么
ThreadLocal
类用来提供线程内部的局部变量。这种变量在多线程环境下访问(通过get和set方法访问)时能保证各个线程的变量相对独立于其他线程内的变量
ThreadLocal与synchronized的区别
虽然ThreadLocal
模式与synchronized
关键字都用于处理多线程并发访问变量的问题, 不过两者处理问题的角度和思路不同。
ThreadLocal强调的是线程数据隔离
的问题,synchronized是多线程共享数据
的问题
synchronized | ThreadLocal | |
---|---|---|
原理 | 同步机制采用’以时间换空间’的方式, 只提供了一份变量,让不同的线程排队访问 | ThreadLocal采用’以空间换时间’的方式, 为每一个线程都提供了一份变量的副本,从而实现同时访问而相不干扰 |
侧重点 | 多个线程之间访问资源的同步 | 多线程中让每个线程之间的数据相互隔离 |
ThreadLocal的内部结构
早期设计
每个ThreadLocal
都创建一个Map,然后用线程作为Map的key,要存储的局部变量作为Map的value,这样就能达到各个线程的局部变量隔离的效果。这是最简单的设计方法,JDK最早期的ThreadLocal 确实是这样设计的,但现在早已不是了
JDK8设计
JDK后面优化了设计方案,在JDK8中 ThreadLocal
的设计是:每个Thread维护一个ThreadLocalMap
,这个Map的key
是ThreadLocal实例本身,value
才是真正要存储的值Object。
具体的过程是这样的:
- 每个Thread线程内部都有一个Map (ThreadLocalMap)
- Map里面存储ThreadLocal对象(key)和线程的变量副本(value)
- Thread内部的Map是由ThreadLocal维护的,由ThreadLocal负责向map获取和设置线程的变量值。
- 对于不同的线程,每次获取副本值时,别的线程并不能获取到当前线程的副本值,形成了副本的隔离,互不干扰
设计的好处
-
这样设计之后每个Map存储的Entry数量就会变少。因为之前的存储数量由Thread的数量决定,现在是由
ThreadLocal
的数量决定。在实际运用当中,往往ThreadLocal的数量要少于Thread的数量。 -
当
Thread
销毁之后,对应的ThreadLocalMap
也会随之销毁,能减少内存的使用
弱引用和内存泄漏
弱引用(WeakReference)
,垃圾回收器一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
假设在业务代码中使用完ThreadLocal ,threadLocal Ref被回收了。
由于ThreadLocalMap只持有ThreadLocal的弱引用,没有任何强引用指向threadlocal实例, 所以threadlocal就可以顺利被gc回收,此时Entry中的key=null。
但是在没有手动删除这个Entry以及CurrentThread依然运行的前提下,也存在有强引用链 threadRef->currentThread->threadLocalMap->entry -> value ,value不会被回收, 而这块value永远不会被访问到了,导致value内存泄漏。
也就是说,ThreadLocalMap中的key使用了弱引用, 也有可能内存泄漏
ThreadLocal内存泄漏的根源是
:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏。
防止内存泄漏
-
只要在使用完ThreadLocal,调用其remove方法删除对应的Entry,就能避免内存泄漏。
-
由于ThreadLocalMap是Thread的一个属性,被当前线程所引用,所以它的生命周期跟Thread一样长。那么在使用完ThreadLocal之后,如果当前Thread也随之执行结束,ThreadLocalMap自然也会被gc回收,从根源上避免了内存泄漏。这种方式显然不好控制,特别是使用线程池的时候,线程结束是不会销毁的
为什么使用弱引用
在ThreadLocalMap
中的set/getEntry
方法中,会对key为null(也即是ThreadLocal为null)进行判断,如果为null的话,那么是会对value置为null的。
这就意味着使用完ThreadLocal,CurrentThread依然运行的前提下,就算忘记调用remove方法,弱引用比强引用可以多一层保障:弱引用的ThreadLocal会被回收,对应的value在下一次ThreadLocalMap调用set,get,remove中的任一方法的时候会被清除,从而避免内存泄漏。
哈希冲突
ThreadLocalMap使用线性探测法(开放寻址)
来解决哈希冲突的。
该方法一次探测下一个地址,直到有空的地址后插入,若整个空间都找不到空余的地址,则会进行rehash
举个例子,假设当前table长度为16,也就是说如果计算出来key的hash值为14,如果table[14]上已经有值,并且其key与当前key不一致,那么就发生了hash冲突,这个时候将14加1得到15,取table[15]进行判断,这个时候如果还是冲突会回到0,取table[0],以此类推,直到可以插入。
按照上面的描述,可以把Entry[] table看成一个环形数组
。
为什么使用开放寻址法
链表法指针需要额外的空间,故当结点规模较小时,开放寻址法较为节省空间
,而若将节省的指针空间用来扩大散列表的规模,可使装填因子变小,这又减少了开放寻址法中的冲突,从而提高平均查找速度。
__EOF__

本文链接:https://www.cnblogs.com/mpyidudu/p/15843216.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现