彻底搞懂-ThreadLocal的前世今生

一. 设计原理

可以通俗的理解,方法里有方法的局部变量、类里有类的局部变量,而线程也可以有线程的局部变量,他就是ThreadLocal。每个线程访问ThreadLocal变量时,都是访问自己内部的变量副本,线程之间无法互相访问

ThreadLocal的设计思路其实是应用了线程封闭的思想,来解决线程间的对象安全问题,将对象封闭在线程内部,也就不存在并发,对象就是安全的。

由此我们可以想到,为了实现这个目的,每个线程内部,应该持有一个属于自己的Entry,我们可以对它进行set get操作。实际上的设计也确实如此,但我们不可能只设计一个Entry,那他的作用就太小了,而是应该设计一个Map,我们可以在这个Map中随意的放置Entry。

好,存储方法设计完成后,我们还需要一个特殊的声明方法,让每个线程在操作时能够知道,我们声明的变量,要求每个线程放在它自己的Map中,考虑到这种类型的变量,是“线程本地”的,所以就叫它ThreadLocal。使用时,我们通过施加泛型,如ThreadLocal来标识真实的数据类型。有了它以后,我们的Entry中的key就不需要单独定义了,只需要将ThreadLocal对象作为Entry的key就可以了。

每个线程使用这个ThreadLocal变量时,应当首先看自己有没有创建Map对象,如果没创建,就创建一个,如果已经创建了,就直接把ThreadLocal对象作为key,放置在自己的私有Map中即可。就像下边这样:

    public static void main(String[] args) {
        ThreadLocal<String> name = new ThreadLocal<>();
        new Thread(
                () -> {
                    name.set(Thread.currentThread().getName());
                    System.out.println(name.get());
                }
        ).start();
        System.out.println(name.get());
    }

非常完美,下边我们从内存模型角度画个图,梳理一下上边的设计:

在这里插入图片描述

1.每个线程Thread内部持有一个ThreadLocalMap,其中有一个Entry数组,里边存储着所有的线程私有Entry。
2.每个Entry中,key是ThreadLocal对象的引用,value则是对真实对象值的引用。
这样,我们就达成了目的:每个线程对ThreadLocal变量操作时,实际上都是操作的自己内部的变量副本,线程将对象封闭在自己内部,实现对象安全。

二. 问题分析

上边这个设计看起来很完美,但我们需要再仔细思考一下,我们上边只考虑了set、get对象的场景,可当我们不需要再使用这个ThreadLocal对象时,会怎样呢?

我们肯定首先希望jvm回收掉ThreadLocal对象,假设我们操作ThreadLocal的引用 = null,希望通过不可达使ThreadLocal对象被回收,但事实会这样吗?不会,因为ThreadLocal还在被Entry的key引用,因此,官方在设计Entry的key与ThreadLocal对象的引用关系时,设计成了弱引用,使得ThreadLocal对象很容易被回收。回收掉ThreadLocal对象以后,其对应在Entry中的key自然也会变成null。那这个Entry会被回收吗?

答案也是否定的,因为Entry对象还被ThreadLocalMap引用,ThreadLocalMap又被Thread引用。这意味着,只要线程不关闭,这些已经分配的value就不会被回收,并且也永远访问不到,有相关基础的同学这时应该已经意识到,这就是内存泄漏。

官方设计ThreadLocal时,自然也考虑到了这一问题,所以他们在ThreadLocal中所有的操作,如set、get、remove时,都会去自动清理这些key为null的entry,以此缓解内存泄漏问题。当然这一切也依赖我们良好的使用习惯,那就是用完ThreadLocal对象后,记得要remove()掉.

注意: Entry中的key与ThreadLocal对象引用关系设计成弱引用,以及ThreadLocal各种操作自动清理key为null的Entry,都是官方为了缓解内存泄漏而做的补救,有些文章认为弱引用是导致内存泄漏的原因,这是不对的。

三. 应用场景

ThreadLocal设计的如此复杂,甚至有内存泄漏风险,那它是为了什么而设计的呢?我们在什么场景下可以使用它呢?
首先,ThreadLocal的使用场景非常明确,它适用于那些,需要在线程间隔离,但在同一线程内的多个方法或类间共享的变量。一般我们用它保存一些线程上下文信息,例如,请求ID,事务ID,session等等。Spring的声明式事务就是用它实现的。有些时候,不是必须使用ThreadLocal来解决这些问题,只是ThreadLocal实现来的更为优雅。
还有一个经典的场景是,微服务中traceId的传递,在线程内部,使用ThreadLocal,在线程间采用InheritableThreadLocal(下文会提到),在服务之间,则将traceId添加到http请求的header中,由被调用端做拦截。

四. 扩展

使用ThreadLocal,除了在单一线程内用于变量共享,还可以用于父子线程共享,这里就涉及到ThreadLocal的继承了,默认的ThreadLocal,是无法在父子线程间继承的,但InheritableThreadLocal可以,InheritableThreadLocal是ThreadLocal的子类,他重写了几个ThreadLocal的方法,配合Thread类实现了当一个线程被创建init时,会去检查父进程的InheritableThreadLocal,如果有值就会copy一个引用给自己,使其可以被继承。

但是使用InheritableThreadLocal也面临很多问题,例如由于该变量可以在父子线程共享,那子进程的修改就会对父进程造成影响,使得该变量不再安全。另一方面,在使用线程池时,由于是复用已有线程,没有init阶段,InheritableThreadLocal也无法被继承下来。使用场景比较窄,需要考虑的问题会比较多。

面对InheritableThreadLocal在池化下的各种不足,阿里开源了一个新的类,名为TransmittableThreadLocal,它继承并扩展了InheritableThreadLocal,补足了其在池化场景下的缺陷。其主要思路,是将任务提交到线程池前,把线程的TransmittableThreadLocal变量保存到需要执行的任务内,在执行时,再拿出来放置给执行任务的线程,以此实现池化场景下的继承。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
FastThreadLocal 是 Netty 中的一个优化版 ThreadLocal 实现。与 JDK 自带的 ThreadLocal 相比,FastThreadLocal 在性能上有所提升。 FastThreadLocal 的性能优势主要体现在以下几个方面: 1. 线程安全性:FastThreadLocal 使用了一种高效的方式来保证线程安全,避免了使用锁的开销,使得在高并发场景下性能更好。 2. 内存占用:FastThreadLocal 的内部数据结构更加紧凑,占用的内存更少,减少了对堆内存的占用,提高了内存的利用效率。 3. 访问速度:FastThreadLocal 在访问时,使用了直接索引的方式,避免了哈希表查找的开销,使得访问速度更快。 在 Netty 源码中,FastThreadLocal 主要被用于优化线程的局部变量存储,提高线程之间的数据隔离性和访问效率。通过使用 FastThreadLocal,Netty 在高性能的网络通信中能够更好地管理线程的局部变量,提供更高的性能和并发能力。 引用中提到的代码片段展示了 Netty 中的 InternalThreadLocalMap 的获取方式。如果当前线程是 FastThreadLocalThread 类型的线程,那么就直接调用 fastGet 方法来获取 InternalThreadLocalMap 实例;否则,调用 slowGet 方法来获取。 fastGet 方法中,会先尝试获取线程的 threadLocalMap 属性,如果不存在则创建一个新的 InternalThreadLocalMap,并设置为线程的 threadLocalMap 属性。最后返回获取到的 threadLocalMap。 slowGet 方法中,通过调用 UnpaddedInternalThreadLocalMap.slowThreadLocalMap 的 get 方法来获取 InternalThreadLocalMap 实例。如果获取到的实例为 null,则创建一个新的 InternalThreadLocalMap,并将其设置到 slowThreadLocalMap 中。最后返回获取到的 InternalThreadLocalMap。 综上所述,FastThreadLocal 是 Netty 中为了优化线程局部变量存储而设计的一种高性能的 ThreadLocal 实现。它通过减少锁的开销、优化内存占用和加快访问速度来提升性能。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [FastThreadLocal源码分析](https://blog.csdn.net/lvlei19911108/article/details/118021402)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* [Netty 高性能之道 FastThreadLocal 源码分析(快且安全)](https://blog.csdn.net/weixin_33871366/article/details/94653953)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值