ThreadLocal,InheritableThreadLocal 使用及原理

一.ThreadLocal使用方法及使用场景

        1.ThreadLocal介绍:

                先说八股文中的说法:ThreadLocal提供了线程本地变量,如果你创建了一个 ThreadLocal 变量,那么访问这个变量的每个线程都会有这个变量的一个本地副本。当多个线程操作这个变量时,实际操作的是自己本地内存里面的变量,从而避免了线程安全问题。

                再说个人的理解:ThreadLocal更类似一个map,每个线程put进去数据,然后在线程的生命周期内,都可以通过get方法从threadLocal对象中拿到自己之前put的值,它不是一个用于解决多线程间内存安全的技术,而是一个多线程访问同一块代码区,如何控制线程变量的技术。

          2.ThreadLocal使用方法及正确使用场景:

                1)核心API:

// 新建一个ThreadLocal对象,允许泛型,可以以任意类对象创建,也可以直接以Object类型创建
ThreadLocal<User> threadLocal = new ThreadLocal<>();
// 向threadLocal中存入对象user
threadLocal.set(user);
// 从threadLocal中获取user对象,线程的整个生命周期中,都可以通过threadLocal.get()获取到之前set过的对象
User gotUser = threadLocal.get();
// 重要!! 清理当前线程在threadLocal对象中set的值,如果不进行remove,有内存泄露风险
threadLocal.remove();

              2)使用方法及对ThreadLocal使用的正确理解:

先看两段代码

左侧代码,有一个共享变量 user,初始化时设置user名字为小张。A线程将user放入了threadLocal,B线程改变了user的name值为老赵。

右侧代码,有一个共享变量 user,初始化时设置user名字为小张。A线程将user先进行了深拷贝,然后放入了threadLocal,B线程改变了user的name值为老赵。

左边代码打印结果是 老赵 ,右边代码结果打印为小张。

以上代码说明,threadLocal并不能保证共享变量的内存安全!threadLocal.set()不是为我们提供了一个所谓的变量副本!

那么,既然 threadLocal并不能保证数据的线程安全,它到底是做什么的?答案是,它是为了降低线程在生命周期内,调用多个函数或组件这个过程中值传递的复杂的。说人话,就是为了方便传值!

那么,使用threadLocal的意义在哪里?既然它是值传递,我们直接传递值不就好了?一个线程中,多个方法调用时,我们在每个方法入参中加一个参数,不是也能达到同样目的?是的,可以达到,没毛病!

但是,当我们写一些组件,组件的上下游之间需要传值,而组件中间部分由其他调用者控制时,我们是不希望组件的参数暴露给调用者,调用者也不希望他的写的方法还需要去关注组件的参数。举一个具体的例子,在我们做SpringBoot项时,我们会自己定义一些AOP切面,对某个方法进行增强,其中,前置和后置通知是我们来完成的代码,而业务代码由其他人完成,这种情况下,如果前置和后置中需要进行值传递,使用ThreadLocal就是一个很好的选择。

依旧是以一段代码来阐述:

二.ThreadLocal, InheritableThreadLocal 的原理

1.ThreadLocal的原理:

        其实,ThreadLocal本质可以理解成一个Map结构,逻辑上,它的原理就是:

        Map<线程,变量> threadLoacal;

        我们调用threadLoacal.set(变量) 时候,其实逻辑上就可以理解为 map.set(当前线程, 变量)

        我们可以简单的理解它是这样一个结构:

         以上是threadLocal逻辑上的原理,但是实际上,threadLoca为了防止内存泄漏,以及实现父子线程通讯,做了很多脑洞大开的设计,认真看一下threadLoca的源码,那是真的掉头发!

首先,逻辑上,我们使用时候感觉threadLocal对象好像有一个map,通过线程能得到我们set的数据,实际上,这个数据存在Thread(线程对象)下,看一下set的源码:

可以看到,Thread中存了一个ThreadLocal.ThreadLocalMap对象,而使用ThreadLocal的set的时候,实际上是Thread.currentThread()获取当前线程,然后set到了线程的这个map对象中!

再看一下ThreadLocalMap这个对象,它是ThreadLocal的一个内部类

这里有几个很有趣的设计,

首先,用table,然后在set的时候,根据threadLoca对象的hash做映射,从而完成一个Map的设计。

其次,注意这个弱引用WeakReference,Entry对象实现弱引用,并以threadLocal对象作为一个标识。这么做的目的,其实是为了让threadLocal对象和value对应变量对象的生命周期解耦!threadlocal和它所set的值的生命周期完全分离!(**这个地方比较难理解,本篇不展开说。基础比较弱的道友建议稍微看一下WeakReference,然后理解我这句话就行。至于如何具体实现的分离,建议基础强的道友详细看一下ThreadLocalMap的源码,并记住,threadLocal和Thread是多对多关系,而多个threadLocal是可以set同一个value值,然后基于这种场景去理解一下为什么要做到value的生命周期,threadLocal对象的生命周期互相不影响)。

总结:线程Thread中存放了一个ThreadLocalMap对象,当threadLocal对一个value进行set/get时候,先从Thread中获取一个ThreadLocalMap,然后以当前的threadLocal对象为key,将value对象存入或者取出,ThreadLocalMap的key设计成了弱引用,从而让threadLocal对象的生命周期和value对象的生命周期互不影响。

2.InheritableThreadLocal 的原理

        InheritableThreadLocal 是ThreadLocal的一个子类,它可以获取到父线程的InheritableThreadLocal 中的数据,其实现原理很简单。Thread中存了inheritableThreadLocals这个对象,然后线程在新建时,会从它的父线程中把父线程的值InheritableThreadLocal的value值抄下来,这个过程发生在线程的创建过程!

3. 关于ThreadLocal的内存泄漏

        其实,ThreadLocal设计ThreadLocalMap以及在Thread中存ThreadLocalMap。而不是像我们在逻辑上理解的,ThreadLocal中放一个Map就是为了解决内存泄漏,我们使用threadLocal.set(value)时候,value的生命周期随线程,而threadLocal的生命周期随它所在的线程,理论上在线程能够正常销毁时候是不存在内存溢出的!不存在!

        但是,我们使用ThreadLocal时候,还是认为它有溢出风险,原因是我们基本上使用的都是线程池,尤其在springboot项目中,我们的请求都是在一个线程池中,如果不及时进行thread.remove的话,会出现一些奇怪的bug,这些bug重启服务就好了,但是点击几次又出现,原因就是线程池中线程一直存活,一个线程使用过threadLocal后,没有remove,等到下次继续使用这个线程时候,如果进行了threadLocald的get就会拿到以前的值。所以,无论是为了安全,还是为了bug,使用ThreadLocal一定要记得remove

  • 19
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值