探索ThreadLocal

特点

  • ThreadLocal是一个线程内部的变量,只在本线程中使用,隔离其他线程
  • ThreadLocal内部维护了一个ThreadLocalMap
  • Thread内部引用了ThreadLocalMap
  • ThreadLocalMap可以保存键值对,但是一个ThreadLocal只能保存一个值,并且各个线程数据互不干扰
  • ThreadLocalMap存储时的key永远为当前的ThreadLocal
  • ThreadLocalMap存储时的key是弱引用的
    ThreadLocalMap
    每个ThreadLocal只能存储一个数据,如果需要存储多个值的话,可以定义多个ThreadLocal。ThreadLocal在内部维护了一个ThreadLocalMap用来存储这些值。
    ThreadLocalMap并没有去实现Map接口,它定义了一个Entry数组,每个Entry以<key,value>的形式来保存值,其中key为当前ThreadLocal本身,value为要保存的值。

注意Entry继承了WeakReference,它的key是弱引用的,会被垃圾回收掉,所以会存在key为null的情况

ThreadLocalMap提供了三个方法:

  • set():以当前ThreadLocal为key存放值
  • get():以当前ThreadLocal为key获取存放的值
  • remove():清除数据
    set()方法
  • 获取当前线程Thread.currentThread()
  • 获取当前线程的ThreadLocalMap
  • 判断ThreadLocalMap是否存在
  • 不存在的,通过createMap,初始化一个ThreadLocalMap,并赋值
  • 存在的,将当前ThreadLocal作为key,进行插入操作:
  • 通过ThreadLocal的哈希值获取要插入的位置
  • 如果当前位置的Entry为空,直接在该位置初始化一个Entry对象来实现插入操作;
  • 如果当前位置Entry的key和要设置的key相同,则覆盖原来的value
  • 如果当前位置Entry的key为null:
  • 循环获取下一位置
  • 如果key和要设置的key相同,则覆盖这个位置的值,并将这个位置和要插入点的entry互换,清理key为null的值;
  • 如果当前位置的Entry为null,退出循环,并在当前位置生成一个新的entry,并清理key为null的值;
    get()方法
  • 获取当前线程Thread.currentThread()
  • 从当前线程获取ThreadLocalMap
  • 判断ThreadLocalMap是否存在
  • 不存在的调用setInitialValue进行初始化,并返回null;
  • 存在的,则以当前ThreadLocal作为key获取值:
  • 通过ThreadLocal的哈希值获取要获取值的位置
  • 当前位置的entry存在且key相同的,直接返回当前值;
  • 当前位置的entry存在且当前key为null的,执行清理重置方法
  • 循环获取下一位置的entry进行对比,如果下一位置的key相同,则返回该值;
  • 如果下一位置的entry为null,则说明该值不存在退出循环返回null;
    remove()方法
  • 清理当前ThreadLocal对应的Entry对象。并调用清理重置方法。

清理重置方法

  • 处理区间:Entry数组当前位置到下一个不为null的Entry之间的数据;
  • 清理key为null的Entry:value设为null,Entry设为null;
  • 重置key不为null的Entry:
  • 通过key的哈希值获取在Entry的数组的索引h;
  • 如果h和当前Entry的索引不一致进行位置重置:
  • 将当前位置的Entry设为null
  • 从h处开始往后找到首个Entry为null的位置
  • 将找到的新位置处的Entry设为原来的entry
    Hash冲突
    ThreadLocalMap并没有实现Map接口,它不是通过链表的形式去避免Hash冲突的,而是通过后移的方式去实现。set方法时,如果当前要存放的位置的key和要设置的key不一致,则会对下一个位置进行判断,直到找到key相同或者为null或者Entry为null的位置。
    内存泄漏问题
    在实际的项目中,我们的线程一般都是由线程池来管理的,线程会一直存在,ThreadLocalMap的value就有可能得不到回收,发送内存泄漏。为了处理这一问题,ThreadLocal的get()、set()方法都有可能会清除key为null的Entry对象。安全起见,当我们使用完后应该手动调用remove()方法清理掉数据。
    用途
    全局变量
    某些数据比如用户ID,很可能在整条业务线上多个方法中都需要用到,如果通过方法参数的形式一层一层的传递下去,整体代码显得凌乱不优雅,这时可以通过ThreadLocal的方式存储。通常可以通过AOP或者拦截器的方式进行赋值,执行完业务逻辑之后调用remove()方法。
private final static ThreadLocal<UserInfo> TL_USER = new ThreadLocal<>();TL_USER.set(userInfo);UserInfo userInfo = TL_USER.get();TL_USER.remove();

独享对象
在实际项目中我们通常会将时间相关的方法写在一个工具类中,往往会用到SimpleDateFormat进行格式化,它是线程不安全的。可以通过ThreadLocal来实现独享对象

private static ThreadLocal<SimpleDateFormat> simpleDateFormatThreadLocal = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"));

以上内容都是我自己的一些感想,分享出来欢迎大家指正,顺便求一波关注,里面的资料各位小伙伴关注我后私信【Java】就可以免费领取~

作者:俞大仙
出处:https://juejin.im/post/5e536cf7f265da57584da272

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值