ThreadLocal & InheritableThreadLocal

       ThreadLocal这个类给线程提供了一个本地变量,这个变量是该线程自己拥有,各线程间不共享。在该线程存活和ThreadLocal实例能访问的时候,保存了对这个变量副本的引用。当线程消失的时候,所有的本地实例都会被GC。并且建议ThreadLocal最好是使用 private static 修饰。

       每个线程Thread里面都各有一个类似MAP的成员变量(变量名为threadLocals),当我们使用一个ThreadLocal实例去设置值的时候,它首先会获取到当前线程Thread,然后获取该Thread中的threadLocals并且把我们需要和线程绑定的值放入该Map中。threadLocals的key为ThreadLocal引用(这也是为什么ThreadLocal要定义成private static),value即为需要保存的值的引用,值的类型就是ThreadLocal定义的类型,可以也是个Map,或其它任何对象。value放在了线程当中,随着线程的生命周期生存,线程死亡,value回收。一个线程操作过多少个ThreadLocal对象,则该线程当中的threadLocals实际存储就有多少个Key-Value对。而当使用线程池的时候,我们应该在改线程使用完该ThreadLocal的时候自觉地调用remove方法清空Entry,这会是一个非常好的习惯。

       threadLocals中Key都是弱引用,这代表他可能将会很快被GC掉,ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用引用他,那么系统gc的时候,这个ThreadLocal很可能会被回收。也就是说除了在ThreadLocalMap中的引用外,在其它地方如果没有强引用就随时会被GC。但一般使用ThreadLocal的时候都会定义一个强引用,所以正常使用不必担心。ThreadLocal相关的类本质上都是在操作当前线程相关成员变量。

       InheritableThreadLocal是为了解决子线程获得父线程本地变量的需求,继承自ThreadLocal。如果你使用InheritableThreadLocal,那么保存的所有东西都已经不在原来的thradLocals里面,而是在一个新的叫inheritableThreadLocals变量中。意思就是说每个线程Thread里面还有一个Map变量,名叫inheritableThreadLocals,它保存的是需要传递的引用(通过InheritableThreadLocal设置的线程变量)。

       InheritableThreadLocal是如何实现在子线程中能拿到当前父线程中的值的呢?在new Thread的时候,会copy父线程parent的map,创建一个新的map赋值给新线程的inheritableThreadLocals,而且在copy过程中是浅拷贝,key和value都是原来的引用地址。复写了getMap(Thread)和CreateMap()方法,所以get值得时候,就可以在getMap(t)的时候就会从inheritableThreadLocals中拿到map对象,从而实现了可以拿到父线程ThreadLocal中的值。

       这种父子传递的需求还是有些比较重要的应用场景,如上下文传递(用户标识、事务等),调用日志跟踪等。Log4j中的MDC就是基于InheritableThreadLocal实现。但一般来说我们用线程池比较多,线程池会缓存线程,重复使用,线程可能会执行不同的任务。这样一来InheritableThreadLocal的上下文传递就达不到正确的效果。

       阿里有个开源项目就是为了解决线程池中变量传递,它里面有个叫TransmittableThreadLocal的类,继承于InheritableThreadLocal。它通过包装返回Runnable的方式代理了run方法,在run之前copy装载线程变量,run之后清除线程变量,来实现此功能。TransmittableThreadLocal除了继承过来的线程Map,它还定义了一个名叫holder的InheritableThreadLocal静态变量,也就是说TransmittableThreadLocal有两套线程变量。

       先调用TransmittableThreadLocal.set保存线程变量,除了正常保存外,还会保存到holder里。其实这么设置一下,当前线程的Map里就有两个Key-Value了,一个是TransmittableThreadLocal本身的,一个是holder的,并且holder真实的Value是一个WeakHashMap(也是弱引用),而且这个WeakHashMap会把TransmittableThreadLocal引用本身当做Key保存起来。

       然后在往线程池提交任务的时候,包装Runnable返回TtlRunnable,这其中会调用TransmittableThreadLocal.copy()把当前线程所有以TransmittableThreadLocal定义的线程变量引用复制到TtlRunnable.copiedRef变量上。这里就用到了holder,这个holder的作用相当于Thread里再添加了一个transmittableThreadLocals的Map变量,最终效果是差不多的,只是因为TransmittableThreadLocal是第三方库,不太可能修改JDK的Thread类,所以才出此下策。holder的WeakHashMap里保存了当前线程所有的TransmittableThreadLocal引用,有了TransmittableThreadLocal引用,就可以获取到它们保存的Value。

       最后在线程池具体线程执行任务(run)的时候,run之前会调用backupAndSetToCopied,备份并清除原有的线程变量,然后再遍历copied里的TransmittableThreadLocal引用,并使用TransmittableThreadLocal引用的set重新设置值。此时设置的对象是线程池当中执行任务线程的本地变量,因为TransmittableThreadLocal永远都操作的是当前线程上的inheritableThreadLocals变量。说白了就是把外面传过进来的变量设置到当前执行任务的线程上去。run之后调用restoreBackup清除当前线程上被copied设置的变量并还原backup。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值