ThreadLocal源码解析

首先几点疑问:

A、threadLocal无论set多少个值,最终取出的都是最后set的值,为什么threadLocalMap中还要维护一个entry[]数组?

答:产生这个疑问的原因是没有考虑到一个线程可能创建多个threadLocal的情况,那么多个threadLocal set的值需要存放不同的位置

B、为什么entry中的key(即threadLocal实例)会为null?

答:因为可能创建一个匿名的threadLocal或者手动的将threalocal引用置为null,这样在gc回收的时候会将此回收

C、entry是按顺序存放的吗?

答案:是的,不过是从根据hashCode值和当前数组长度计算出的索引开始

1、ThreadLocal原理

在第一次set值得时候,会首先获取当前线程对象即Thread t = Thread.currentThread,取出t中的Thread.ThreaLocalMap变量,判断如果为null,则为该线程创建一个ThreadLocalMap实例,如果不为null,则对ThreadLocalMap中的entry[]进行操作,该entry[]数组存放的就是当前线程下的每个threadLocal实例set的值。所以每个线程都会独有一份threadLocalMap实例,互不影响

注:理论上第一次set值的时候threadLocalMap应该为null,但是经调试发现,在创建ThreadLocal实例的时候,就已经初始化了threadLocalMap,而且entry[]数组中存有两个entry,Key为当前threadLocal实例,value为时间戳

2、源码解析

2.0:构造函数

构造函数很干净,但是真的没有任何操作吗?调试的时候发现构造完之后Thread实例中的threadLocalMap已经初始化完成了,而且entry[]数组中已经存在两个entry,这波操作待研究

2.1:ThreadLocal的主要成员变量、静态变量、静态内部类(ThreadLocalMap)

threadLocalHashCode该变量贯穿全文,entry[]数组的索引就是根据该hashcode值和数组长度进行计算

nextHashCode该变量用来生成threadLocalHashCode

nextHashCode():生threadLocalHashCode的过程

ThreadLocalMap静态内部类:entry[]用来存放当前线程中每个threadLocal set的值

注:threadLocalHashCode是定值,是定值,是定值。AtomicInteger底层使用的unsafe的cas操作,unsafe很重要的一个类,在并发操作中很多都有涉及,比如ReentranLock、Atomic**、线程池

2.2:set方法

首先得到当前线程对象,进而获取当前线程的theraLocalMap变量,判断该变量是否null,如果不为null,则调用该theraLocalMap的set方法,为null则为该线程创建一个ThreadLocalMap实例

想要存值,必须知道要存放在entry[]的哪个索引下,i即是可能存放的索引,由hashcode值和数组当前长度计算所得(因为是&操作,所以计算出来的数值一定是小于当前数组长度的),为什么说可能,因为当前可能有一个以上的ThreadLocal实例set值,因为threadLocalHashCode是固定的,所以计算出来的索引值也都是一样的,第一个threadLocal实例set值,存在该索引下,第二个threadLocal再次set时肯定不能把第一个的entry给覆盖了(除非第一个threadLocal已经被回收),因此第二个threadLocal set的值要向后去寻找没有值的索引,找到了null值的索引则跳出for,进行存值操作,如果找到了一个threadLocal为null的entry(说明该threadLocal已经被回收),则调用replaceStaleEntry方法,也即是替换掉当前无用的entry值,看replaceStaleEntry方法:

 

首先是向前遍历entry[]数组,查找是否还有无用的entry(key为null,即threadLocal实例已被回收),如果有则记录下该索引,prevIndex()方法是使当前索引-1,如果大于等于0则返回该值,如果小于0则返回数组的末尾索引,再次向前遍历

接下来向后遍历,遇到索引下的值为null的跳出循环(见2.2.1),否则比较entry的key和要存放的threadLocal实例,如果相等(说明之前已经存放过了),更新值,然后调换staleSlot和当前索引下的值(目的是为了让该threadLocal set的值离命中索引更近,减少查询遍历的次数),而后记录下当前索引的值(记录的原因是交换了索引下的值后,当前索引下的entry就为null了),便于接下来清除无效key值的entry,看expungeStaleEntry()方法:

 

首先将staleSlot索引下的值置空,数组值的长度减一

接下来向后遍历,如果碰到了key为null的entry则将其置空,如果碰到key不为null的entry,则将其和命中索引比较,不等于则将其移动到离命中索引最近的位置,最后返回置为null的索引值,将此索引值作为参数传入cleanSomeSlots()方法,看cleanSomeSlots方法:

该方法是去找key为null的entry,然后将其清除

2.2.1:接上:

向下遍历找到了为null的索引,则在该索引下直接存放新的entry,最后判断是不是还有其他key为null的entry,存在则清除无效entry

2.3:get方法

主要是threadLocalMap的getEntry方法,看getEntry

先计算得到索引值(固定),该索引下的entry的key值等于传入的threadLocal,则直接返回,不等于,说明有另一个threadLocal实例也set过值,所以调用getEntryAfterMiss()方法遍历查找,看getEntryAfterMiss方法:

循环遍历,找到则返回,遇到entry的key为null的,就调用清除方法,最终找不到返回null

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值