ThreadLocal详解

ThreadLocal

基本介绍

介绍:

ThreadLocal类用来提供线程内部的局部变量。这种变量在多线程环境下访问时,保证各个线程的变量,相对独立于其他线程内的变量,Thread类的实例通常来说是private static类型的,用于关联线程与线程上下文。

作用:

提供线程内的局部变量,不同的线程之间不会相互干扰,这种变量在线程的生命周期内起作用,减少同一个线程内多个组件之间公共变量传递的复杂度。

总结:

  1. 线程并发:在多线程并发的场景下
  2. 传递数据:我们可以通过ThreadLocal在同一线程内,不同组件中传递公共变量
  3. 线程隔离:每个线程的变量都是线程独立的,不会相互影响。

ThreadLocal类结构

public set(T t):将变量绑定到当前线程中

public public T get():获取当前线程绑定的变量

public setInitialValue():获取当前线程变量的初始值

public remove():移除设置的一个ThreadLocal对象

protected T initialValue():默认返回null,子类可重写的方法,未set()先get()时,此方法会被调用。

ThreadLocal底层存储设计

jdk8中ThreadLocal的设计:每一个Thread维护一个ThreadLocalMap,这个map的key是threadLocal实例本身,value才是真正要存储的值Object。

  1. 每一个Thread线程内部都有一个Map(ThreadLocalMap)
  2. map里面存储ThreadLocal对象,和线程的变量副本(value)。
  3. Thread内部的map是由ThreadLocal维护的,由ThreadLocal负责向map获取和设置线程的变量值。
  4. 不同线程每次在获取副本值时,别的线程不能获取到当前线程的副本值,形成了副本的隔离,互不干扰。
  • 这样设计的优点:
  1. 每一个map存储的entry数量变少,减少hash冲突
  2. Thread销毁时ThreadLocalMap也会随之销毁,减少内存的使用。

ThreadLocalMap底层分析

ThreadLocalMap是ThreadLocal的内部类,没有实现map接口,用独立的方式实现了map功能,其内部的Entry也是独立实现的。底层数组初始容量为16,临界值2/3,二倍扩容

  • 构造方法

    首先创建了一个长度为16的Entry数组,然后计算出firstKey的索引,存储到table中,再设置size,临界值。

//构造方法(ThreadLocal,Value)
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
    //初始化table表,实际上是Entry类型的数组。初始大小为16
    table = new Entry[INITIAL_CAPACITY];
    //计算要索引位置
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    //存储
    table[i] = new Entry(firstKey, firstValue);
    //表示map中存储的值多少
    size = 1;
    //设置临界值2/3
    setThreshold(INITIAL_CAPACITY);
}
  • set(key,value)方法

    a.先计算出传入的key值的索引位置,此位置没有值就直接创建Entry对象插入,size加1,

    之后,调用cleanSomeSlots()方法清理key值为空的Entry对象。若调用此方法后没有清理任何Entry对象,且map中存储值达到了临界值就会进行rehash()的调用。

    b.有值,进入for循环,若两值的key相等,用新value替换旧value,退出此方法。

    若此位置的key为空,表示这是一个过期元素,用新元素替换过期元素,退出此方法。(此处进行了垃圾清理动作防止内存泄漏。即调用cleanSomeSlots()方法)

    for循环的方式:Entry[] table可以看作是一个环形数组,一直向后查找,直到找到空位置,或key相等的值

    ThreadLocalMap使用线性探测法解决哈希冲突的。Entry[] table可以看作是一个环形数组。

    该方法一次探测下一个地址,直到有空的地址后插入,整个空间都找不到空余的地址,则会产生溢出。

    举例:假设当前table的长度为16,若计算出来的索引值为14,且table[14]上已经有值,且其key值与当前值不一样就产生了hash冲突,这时判断table[15],若还是冲突,就继续判断table[0],以此类推直到可以插入。

  • cleanSomeSlots()方法:将key为空,entry不为空的值的value,entry都置为空。

  • rehash()方法:先进行一次全表扫描清理,清理过期的Entry。清理之后长度大于等于原长度的1/2就会进行2倍扩容。resize()方法的调用,

ThreadLocal内存泄露的根本原因:

threadLocalMap的生命周期与Thread一样长,如果没有手动删除对应的key就会导致内存泄漏。

ThreadLocal与Entry之间使用弱引用的原因:

因为在ThreadLocalMap中的set/getEntry方法中会对key进行判断是否为空,为空就将其对应的value也置为空。

当前线程仍在运行的前提下,程序员忘记调用remove()时,弱引用比强引用多了一层保障。当没有强引用引用ThreadLocal之后,只有弱引用的ThreadLocal将被回收掉,这时Entry中的key为空,在下一次ThreadLocalMap调用set/getEntry方法时会将value置为空,回收掉。从而避免内存泄漏。

ThreadLocal与Synchronized的区别:

Synchronized ThreadLocal
原理 同步机制采用以时间换空间的方式,只提供了一份变量,让不同的线程排队访问 ThreadLocal采用以空间换时间的方式,为每一个线程都提供了一份变量的副本,从而实现同时访问而互不干扰
侧重点 多个线程之间访问线程同步 多线程中让每个线程之间的数据相互隔离

posted on 2021-09-03 08:26  凡人精灵  阅读(140)  评论(0编辑  收藏  举报

导航