ThreadLocal

是什么?

首先,我们从名字上来看就是线程本地的意思,可以想到在ThreadLocal内的都是独属于线程本身。实际也是如此,ThreadLocal对象可以提供线程局部变量,ThreadLocal为变量在每个线程中都创建了一个副本,每个线程可以访问自己内部的副本变量。这也意味着多个线程之间互不干扰。

怎么用?

一般而言,看一个对象先从构造方法开始,但是ThreadLocal的构造方法为空实现。
image.png

我们看下真正使用它的Looper类

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

所以其重点应该在于其他的方法上,我们不难发现,既然是储存变量,那么重点就该是查询、增加、删除的方法,分别对应着:get(),set(),remove()方法。

get()方法

image.png

注释其实解释的很明白,获取线程局部变量的当前线程的拷贝。如果不存在,则进行初始化。

  • 首先,获取当前线程,根据当前线程获取Map(ThreadLocal重点,真正存储变量的地方)
  • 如果不为空,则继续查询,是否有对应的实体,如果存在则放回。
  • 如果map为空,或者entry为空时,则调用setInitialValue()方法。

接着看下setInitialValue()方法
image.png

initialValue()方法是子类重写的方法(),设置线程局部变量的初始值。如果我们不想让线程局部变量的返回值为null(即第一次访问时),就需要重写该方法,用以给线程局部变量添加初始值。

当map为空时,则将初始化Map,并将其设置为第一个Entry。如果是Entry为空,则设置Entry。

以下是创建map的方法:
image.png

我们发现创建方法是在Thread类里设置一个全局Map变量。并将当前的这个ThreadLocal对象设置为初始值。

不难发现ThreadLocal仅仅表示变量,而ThreadLocalMap才是真正储存变量的容器,这个我们后文再详细解释。

set()方法

image.png

相较于get()方法,set()朴实了很多,毕竟只要往里面放就可以了。如果map不为空,则直接设置。如果为空,则创建一个Map。

remove() 方法

image.png

remove方法直接使用了ThreadMap的remove方法,ThreadLocal工具人实锤了。

其实这个ThreadLocalMap类都是延时创建的,当我们第一次调用set或者get方法时才会创建。那我们来看看这个最重要的ThreadLocalMap类吧。

ThreadLocalMap

在看这个类源码之前,我们得看一个问题,就是为什么要费劲新建一个容器呢?用原先的容器类,不行吗?

为什么不用HashMap,而是新建一个类呢?

我们不难发现

  • 一、可以定制key的类型,为ThreadLocal类型,且为弱引用是不影响对象被回收的,而HashMap的是强引用
  • 二、在 写数据 和 查数据的时候 ThreadLocalMap会有清理过期数据的功能。(提供解决内存泄漏的一个策略,但不是完全解决)
  • 三、为了清理过期数据,解决hash冲突的方法,从拉链法,改成了线性开发定址法。
  • 四、为了适应hash冲突解决方法的改变,使数据分布更加均匀,ThreadLocalMap重写了hash算法。使用魔数叠加的hash,可以保证hash的分布均匀。 比如一个长度为16的数组,可能分配到[0],[4],[8],[12]的。
基础信息
  • 初始容量:16(为什么一定要是2的倍数,与hashMap的原因类似,一是便于hash的存储,位运算速率会比快,二是有利于扩容)
  • 扩容阈值:2/3
  • hash算法:魔数叠加
  • Entry类型:弱应用 key,正常引用value
resize方法

当扩容达到阈值之后,会触发rehash方法,先清除一轮过期数据,如果清理数据后,数据容量为阈值的3/4,则开启resize方法。

image.png

resize方法,新建一个数组,将旧数组的重新rehash之后,一一放入后,最后更新引用。并重新计算阈值
image.png

get方法
  • 先根据hash与长度按位与找到合适位置,如果这个位置不是,说明发生hash冲突或者不存在,所以需要向后寻找。

image.png

  • 现在就分为两种情况

    • 碰到正常数据,则搜索下一个
    • 碰到null,则开始探测式清理过期逻辑。(这个算是这个类的重要的一个功能)简要的介绍一下流程

      • 开始向后迭代,遇到想要的值就放回。否则遇到正常数据时,判断是否处于正确位置,如果是,则不处理,如果不是,则rehash,重新定位(因为之前可能清理出一部分空slot),所以重新rehash后的index理论上会更接近应该处在的index(或者直接处于正确位置)。遇到key=null时,会将当前位置置为空。一直迭代到slot为空时,结束。

image.png

set方法(理解不够透彻)
  • 正常情况,无冲突且为null,就直接添加。
  • 不为null,也分为两种情况,先碰到可key一致的数据,那么替换返回直接,然后就是抵达过期数据了(其实也就是null),就向后进行查找,如果碰到一个key一致的slot,先更新,然后在于当前的这个slot进行替换,如果一直都没有碰到相同的key,那么直接在当前的这个slot生成这个数据。
  • 然后判断是否要扩容。
与之前版本的区别

在之前的版本中ThreadLocal是维护了一个大Map,Map以Thread为key,分别给每个变量赋值。缺点就是,如果线程过多,会造成Map很大,且不利于GC,现在而言由于是每个线程自己拥有自己的一块map,所以更容易维护与管理。


JathonW
1 声望2 粉丝

精致唯一