栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

ThreadLocal之20+连环问

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

ThreadLocal之20+连环问

1.是什么?(ThreadLocal)

ThreadLocal 是一个线程变量工具类,可以提供线程局部变量

  • ThreadLocal可以为每个线程提供线程局部变量,变量对其他线程而言是隔离的。

  • 为变量在每个线程中都创建了一个副本,每个线程可以访问自己内部的副本变量。

    ps : 可以通过重新 initValue()来自定义初始值

2.特性

ThreadLocal和Synchronized都是为了解决多线程中相同变量的访问冲突问题,不同的点是

  • Synchronized是通过线程等待,牺牲时间来解决访问冲突
  • ThreadLocal是通过每个线程单独一份存储空间,牺牲空间来解决冲突,并且相比于Synchronized,ThreadLocal具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问到想要的值。

ThreadLocal是用冗余的方式换时间,而锁机制则是时间换空间。

3.应用场景(能干什么?)
  • 线程间数据隔离,各线程的 ThreadLocal 互不影响
  • 全链路追踪中的 traceId 或者流程引擎中上下文的传递一般采用 ThreadLocal
  • Spring 事务管理器采用了 ThreadLocal
  • Spring MVC 的 RequestContextHolder 的实现使用了 ThreadLocal
  • 一些ORM框架的Session管理,web系统的会话管理。
  • 使用到类似责任链模式,给每个方法增加一个context上下文参数非常麻烦,可以使用 ThreadLocal传递。

简单总结:
一个ThreadLocal只能保存一个变量的副本,如果需要多个,就得创建多个变量;我们确定使用完需要执行remove避免内存泄漏。

4.使用方式
  • 可通过 ThreadLocal value = new ThreadLocal(); 来使用。
  • 会自动在每一个线程上创建一个 T 的副本,副本之间彼此独立,互不影响,可以用 ThreadLocal 存储一些参数,以便在线程中多个方法中使用,用以代替方法传参的做法。
5.JDK 1.8 实现原理
  • 每个线程对象 Thread 对象内部拥有一个 threadLocals 字段,这个属性会指向一个Thead对象中ThreadLocalMap;
  • ThreadLocalMap 存储的是当前线程与其它 ThreadLocal关联的数据
  • 线程访问某个ThreadLocal对象的get或set 方法的时候都会检测,在get方法获取的时候会判断当前线程的Map 是否有key , 为这个 ThreadLocal对象的Entry数据
    • 若没有 ThreadLocal的initialValue方法会创建一个Entry数据并放入到ThreadLocalMap中
  • 每个线程中都有一个独立的ThreadLocalMap副本,它所存储的值,只能被当前线程读取和修改。
  • ThreadLocalMap存储的键值对中的键是this对象指向的ThreadLocal对象,而值就是你所设置的对象了。
6.JDK 1.7 之前实现原理(简单介绍)
  • 在ThreadLocal维护一个大Map,所有的线程变量都维护在一个Map里面。
7.JDK1.8版本相比之前的设计有什么优势?
  • 线程多,若只维护一个大Map 数据过大不利于维护
  • 新版本线程间数据隔离,每个线程有自己的ThreadLocalMap ,当线程被销毁的时候,就可以在下一次GC回收。
  • ThreadLocalMap中 Entry对应的key是一个弱引用,如果ThreadLocal对象被回收的话,线程还未结束,ThreadLocalMap还未被回收,(ps:弱引用不参与root 算法)
8.为什么ThreadLocal选择自定义一个Map没使用HashMap
  • ThreadLocalMap的 key是特定类型就是 ThreadLocal对象
  • key 是弱引用,HashMap key 是强引用, 弱引用不影响对象被回收。
9.ThreadLocalMap 初始化长度
16
10.大小为什么必须是2的次方数?
  • 与HashMap一样为了方便Hash寻址
  • 因为2的次方数-1之后转换为二进制一定是 1111结尾的二进制数。
  • 若数值与这种二进制数进行 按位 与运算得到的一定是 >=0 且 <= 这个二进制数值的 。比使用取模算法 ,效率高很多
11.扩容的阈值是多少
  • Entry 数组长度的 三分之二
  • 比如:初始化是16 ,阈值则是 10
12.达到扩容阈值后一定会扩容吗?

不会

  • 会先清理过期数据(cleanSomeSlots方法)将key为null的数据移出掉。
  • 若移除后当前map实际容量大小仍然阈值的 >= 75% 则扩容
    • 源码技术公式 size >= threshold - threshold / 4 例如 当前大小 >= 10-10/4
13.扩容过程
  • 首先会创建一个新的数组,长度是当前散列表数组的2倍
  • 遍历老的数组,将其中的数据重新安装Hash算法放入新的数组
  • 更新ThreadLocalMap对象的散列表引用指向新的数组
  • 扩容完成之后,重新计算一下,下次触发扩容的阈值(新数组大小的 2/3 )
14.ThreadLocalMap的get方法逻辑
  • 首先Get方法传进来的是ThreadLocal对象,
  • 根据ThreadLocal的hash值与运算当前数组长度-1,得到下标位置index
  • 若当前位置数据不为空并且key与当前查询的key相同的话返回。
  • 若不是则表示遇到过Hash冲突。
    • ThreadLocaMap 内部没有链表结构,若Hash冲突,是线性的存到后面某个位置。
    • 所以get操作的时候,若第一次没有命中,需要后续查询,直到查到数据或者返回null
15. get第一次没有get到向后查询时遇到过期数据怎么处理
  • 触发一次“探测式”过期数据回收逻辑
  • 从当前桶位节点,向后迭代,将碰到key==null 会把 Entry值置位null
16. get 方法 “探测式”清理过期数据如果碰到正常数据怎么办?
  • 若碰到正常数据会根据key重新计算出来一个index ,看计算出来的index是否等于当前位置,
  • 若相等,则啥也不做
  • 若不相等,说明存数据发生过Hash冲突 。位置被占用了
    • 会继续向计算出来的下标后查找一个不为null的位置存。
17.什么是过期数据
  • ThreadLocalMap内存储的是Entry, Entry有key 和value, key 是弱引用。
  • key 限定是ThreadLocal对象,value是具体的关联数据。
  • 当 key对应的ThreadLocal被回收后,key 是弱引用,key.get()==null 就代表是过期数据
18.ThreadLocalMap的set方法逻辑
  • 从当前线程对象中取TheadLocalMap。
    • 若取不到-则创建ThreadLocalMap并存储初始化值。
  • 首先根据key, hashCode 找到对应的位置。
  • 若当前值为 null,则表示是新添加数据,只需要添加到这个节点。
  • 若不为 null
    • 说明发生hash冲突,线性向数组后。查找到一个目前没有占有的位置插入。
    • 判断当前Key是否与set的 key一致,是则更新
    • 若遇到 key 为空的记录 -执行清理过程,过期数据就替换
19.ThreadLocalMap的remove方法逻辑
  • 从当前线程对象中取TheadLocalMap。
  • 若不为空 则移出以当前 ThreadLocal对象为key的数据
  • 若为空,则什么也不做
20.为什么ThreadLocalMap的Enrty是弱引用?
  • 将Entry的Key设置成弱引用,因为在配合线程池使用的情况下可能会有内存泄露的风险。
  • 设计成弱引用的目的是为了更好地对ThreadLocal进行回收,
  • 将ThreadLocal的强引用置为null后,这时候Entry中的ThreadLocal理应被回收了,
  • 如果Entry的key被设置成强引用则该ThreadLocal就不能被回收,这就是将其设置成弱引用的目的。

那为什么ThreadLocalMap的key要设计成弱引用?
key不设置成弱引用的话就会造成和entry中value一样内存泄漏的场景。

21.内存泄露问题
  • ThreadLocal 在没有外部强引用时, GC 时会被回收。
  • 其实同一个线程也创建了多个ThreadLocal ,共享一个 ThreadLocalMap,ThreadLocal使用完被回收。
  • ThreadLocalMap 中保存的 key 值就变成了 null,而 Entry值 又被 threadLocalMap 对象引用
  • 若线程未执行完ThreadLocalMap 还会存在
  • 若线程Thread 一直不终结的话,value 对象就会一直存在于内存中,也就导致了内存泄漏,直至 Thread 被销毁后,才会被回收
22.如何避免内存泄漏呢?

在使用完 ThreadLocal 变量后,尽量手动 remove 掉,防止 ThreadLocalMap 中 Entry 一直保持对 value 的强引用,导致 value 不能被回收

23.ThreadLocalMap如何解决哈希冲突
  • 如果使用HashCode来计算Hash值是不均匀的,
  • ThreadLocalMap是使用了一种黄金分隔数的方式,来分配Hash,映射到散列表内很均匀。
    • 比如数组长度是16,连续分隔可能是 table[0] table[4] table[8] 这样类似比较均匀。

ThreadLocal中的hash 计算底层

  • AtomicInteger 的 getAndAdd方法,参数是个固定值 0x61c88647。结构非常只用一个数组存储,并没有链表结构,
  • 当出现Hash冲突时采用线性查找的方式,线性查找就是根据初始key的hashcode值确定元素在table数组中的位置,如果发现这个位置上已经有其他key值的元素被占用,则利用固定的算法寻找一定步长的下个位置,依次判断,直至找到能够存放的位置。如果产生多次hash冲突,处理起来就没有HashMap的效率高,为了避免哈希冲突,使用尽量少的threadlocal变量

如果我想共享线程的ThreadLocal数据怎么办?
使用InheritableThreadLocal可以实现多个线程访问ThreadLocal的值,我们在主线程中创建一个InheritableThreadLocal的实例,然后在子线程中得到这个InheritableThreadLocal实例设置的值。

如何实现的呢?
Thread源码中,初始化Thread.init方法,将父级的值做了传递。

24.使用注意事项

10.1注意空指针 (若还未set就 get 默认返回null ,可通过重写 initialValue 自定义默认初始化值)
示例:

public static ThreadLocal myThreadLocal = new ThreadLocal() {
   @Override
   protected String initialValue() {
      return "This is the initial value";
   }
};
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/324699.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号