前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java并发指南:线程隔离并发安全及隐式传参利器ThreadLocal的实现原理分析及注意事项

Java并发指南:线程隔离并发安全及隐式传参利器ThreadLocal的实现原理分析及注意事项

作者头像
崔认知
发布2023-06-20 11:16:07
3780
发布2023-06-20 11:16:07
举报
文章被收录于专栏:nobodynobody

简介


Java为开发者提供的ThreadLocal方便了实现线程隔离隐式传参场景的需求,具体介绍与避坑可以回看博文:

今天主要分析ThreadLocal的实现原理。

ThreadLocal的实现原理分析


下图展示了ThreadLocal、当前线程Thread及线程内部ThreadLocalMap在内存中的引用关系

每个线程对象内部都有一个ThreadLocalMap对象,由于访问权限的设置只能通过ThreadLocal访问,保证操作线程私有且线程安全。

ThreadLocalMap内部是一个哈希表数据结构:

代码语言:javascript
复制
private Entry[] table;

每个哈希元素Entry持有的key是ThreadLocal实例的弱引用,value是ThreadLocal实例关联的数据对象的引用。

源码分析java.lang.ThreadLocal#set方法如何设值:

1、通过Thread.currentThread() 获取当前线程,只会操作当前线程的ThreadLocalMap,保证线程安全;

2、获取当前线程内部持有的ThreadLocalMap;

3、如果哈希map已经初始化,则直接保存java.lang.ThreadLocal.ThreadLocalMap.

Entry< WeakReference< 当前ThreadLocal实例 > ,value>;

4、如果哈希map还没初始化即第一次保存数据,则初始化当前线程的ThreadLocalMap,并设置初始值;

可见,线程内部的ThreadLocalMap是延迟初始化的。

源码分析java.lang.ThreadLocal#get方法如何获取当前线程数据:

1、获取当前线程Thread.currentThread();

2、获取当前线程的ThreadLocalMap;

3、如果当前线程的ThreadLocalMap已经初始化了,则哈希表查询,key参数为当前ThreadLocal实例;

4、如果当前线程的ThreadLocalMap还没有初始化,则创建哈希表,设置初始值;

ThreadLocalMap的实现原理分析


ThreadLocalMap是ThreadLocal的静态内部类,默认包访问级别,这样使得同一个包下的Thread才能声明

代码语言:javascript
复制
/* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;

而且ThreadLocalMap除了一个构造函数的访问权限是包访问级别,其它方法都是私有的,这样使得ThreadLocalMap的操作限制在ThreadLocal类内。

ThreadLocalMap的哈希数组中,保存的是ThreadLocal的弱引用,是为了防止内存泄露。

实例场景分析ThreadLocal的弱引用是如何防止内存泄露的:

如果方法funA被一个线程池中的一个线程线程执行完,内存引用图如下:

方法栈帧退出,使得userInfoContext不再引用到内存ThreadLocal实例,此时内存中的ThreadLocal实例是需要被GC的,但是线程池中的线程如果是核心线程,则生命周期和JVM的生命周期一样的话,线程内部的ThreadLocalMap及内部的Entry都不会被释放,如果Entry的key是强引用,则导致ThreadLocal实例永不会被GC回收,如果Entry的key是弱引用,此时ThreadLocal实例无强引用关联,在GC时会被回收。

虽然Entry的key是弱引用,GC能回收无强引用关联的ThreadLocal实例,但是Entry中的value引用的内存实例还是不能回收的,不过此功能的实现者也考虑到了,在调用任何其它ThreadLocal实例的set、remove方法时,会回收这些Entry的弱引用key为null的value对象及Entry对象实例的清理(设置为null)。

注意:ThreadLocalMap哈希表的实现是与HashMap不同的,冲突处理使用了开放定址法。

ThreadLocal的注意事项


1、尽量使用private static final ThreadLocal<>,使用工具类封装ThreadLocal的set、get、remove方法;

目的是访问权限控制,而且保证单例性。

实例:

代码语言:javascript
复制
org.springframework.web.context.request.RequestContextHolder

2、ThreadLocal使用完务必在finally块执行remove方法;

目的是避免内存泄露。

实例:

代码语言:javascript
复制
org.springframework.web.filter.RequestContextFilter#doFilterInternal

3、ThreadLocal使用之前,根据业务场景务必重新初始化;

目的是避免信息错乱或信息丢失。因为线程池场景下,线程复用,如果不初始化,使用的是上一次设置的值,或者是隐士传参不重新设值,导致线程切换信息丢失。

实例:

代码语言:javascript
复制
org.springframework.web.filter.RequestContextFilter#doFilterInternal
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-10-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 认知科技技术团队 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档