面试官:ThreadLocal 了解吗?
Python 小星:线程局部变量,多线程下能保证各个线程的变量相对独立于其他线程的变量。
面试官:那你说下它是如何保证线程隔离的?
Python 小星:每个线程维护一个 ThreadLocalMap ,ThreadLocalMap 和 HashMap 结构类似,key 是 ThreadLocal,通过 hash 算法取余平均分配到数组上,value 是我们需要的局部变量。有与 key 的唯一性,保证了线程隔离。
面试官:那你说说 ThreadLocalMap 是数组加链表的结构解决冲突的吗?
Python 小星:我的印象里不是链表结构。
面试官:那他是如何解决冲突的?
Python 小星:......
面试官:创建多线程异步执行业务逻辑时,该 ThreadLocal 变量并不能传递到子线程中,如何让父子线程共享变量?
Python 小星:不太清楚
面试官:InheritableThreadLocal 可以下去了解下。今天就先到这,回去等通知吧
什么是 ThreadLocal?(以下简称 TL)
看下JDK源码注释
翻译过来:
TL 用来提供线程内部局部变量。这种变量在多线程环境下访问(通过 get 和 set 方法访问)时能保证各个线程的变量相对独立于其他线程的变量。TL 实例通常来说都是 private static 类型,用于关联线程和线程上下文。
简单说,ThreadLocal 为每个使用该变量的线程提供独立的变量副本。所以每个线程都可以独立地改变自己的副本,而不会影响其他线程所对应的副本,我们通常把这叫“线程隔离”。
ThreadLocal 的基本使用
1、常用方法
2、如果不是 TL,会出现什么问题???
输出结果(多运行几次):
我们可以发现,线程 0 输出了线程 2 的数据。
3、使用 TL
TL 如何实现线程隔离???
1、JDK 早期设计
熟悉 hashmap 的老铁们,比较容易理解:每个 TL 创建一个 Map,然后用线程作为 Map 的 key,要存储的局部变量作为 Map 的 value,这样达到线程隔离的目的。
这么做有一个弊端:
当线程回收时,该线程绑定的变量不能被自动的回收,因为变量存储在 ThreadLocal 里,必须显式的去回收。
2、JDK 1.8 的设计
每个 Thread 维护一个 ThreadLocalMap,这 map 的 key 是 ThreadLocal 实例本身,value 才是真正要存储的变量值。
① 每个 Thread 内部都有一个 ThreadLocalMap
② ThreadLocalMap 里面存储 TL 对象(key) 和 线程的变量副本(value)
③ Thread 内部的 ThreadLocalMap 是由 TL 维护的,由 TL 负责向 ThreadLocalMap 设置和获取变量值
④ 对于不同的线程,每次获取副本值时,别的线程并不能获取当前线程的副本值,形成副本的隔离,互不干扰
好处:
① 每个 ThreadLocalMap 存储的 Entry 数量变少
② 当 Thread 销毁时,ThreadLocalMap 也随之销毁,减少内存的使用
TL 源码
1、set 方法
① 首先获取当前线程,并根据当前线程获取一个 map
② 如果获取的 map 不为空,则将参数设置到 map 中(当前的 TL 的引用设置为 key)
③ 如果 map 为空,则给该线程创建 map ,并设置初始值
2、get 方法
① 获取当前线程,然后根据当前线程获取 map
② 如果 map 不为空,则以 TL 的引用为 key 获取 map 中对应的 Entry e,否则到第 ④ 步
③ 如果 e 不为空,获取对应的 value 值,否则到第 ④ 步
④ map 为空 或者 e 为空,通过 initialValue ,也就是 NULL,然后用 TL 的引用和 value 作为 firstKey 和 firstValue 创建新的 map
总结:获取当前线程的 ThreadLocalMap,如果存在则返回值,不存在则创建并返回值。
3、remove
4、ThreadLocalMap
ThreadLocalMap 是 TL 的内部类,没有实现 map 接口。
从图中,我们可以发现 SHI 和 HashMap 类似。
Entry 继承 WeakReference,也就是说 key 是弱引用。
弱引用和内存泄漏
1、弱引用相关概念
① java 引用
4 种类型:强、弱、软、虚
② 强引用和弱引用
【强引用】:最常见的 new 一个对象,只要强引用指向一个对象,GC 就不会回收
【弱引用】:GC 一旦发现弱引用的对象,不管当前内存空间足够与否,都会回收它的内存
2、内存溢出和内存泄漏
① 内存溢出(memory overflow)
没有足够的内存提供申请者使用。就像一个开水瓶,当你倒水时,如果大于水瓶容量,水就会溢出来。
② 内存泄漏(memory leak)
动态分配的堆内存由于某种原因未释放或者无法释放,造成系统内存的浪费,内存泄漏的堆积将造成内存泄漏。
为什么 TL 里使用弱引用?
首先我们明确 key 是使用了弱引用,当把 threadlocal 实例置为 null 以后,没有任何强引用指向 threadlocal 实例,所以 threadlocal 就可以顺利被 gc 。
我们从图中也能看到虽然 key 被 gc 回收了,但是 value 还在呀。因为存在一条从 current thread 连接过来的强引用。只有当前 thread 结束以后,current thread 就不会存在栈中,强引用断开,Current Thread, Map, value 将全部被 GC 回收。
有人会问:无论是强引用还是弱引用,都会出现内存泄漏?用弱引用的好处到底体现在哪?
弱引用会将 key 设置为 null,当使用 map 的 get 和 set 方法时,会判断 key 是否为 null ,如果为 null,则将 value 设置为 null。弱引用比强引用在 thread 结束之前多一层屏障。
如何解决内存泄漏问题
当线程的某个 localThread 使用完,然后调用 threadlocal 的 remove 方法。
ThreadLocalMap 和 HashMap 的区别?
ThreadLocalMap 和 HashMap 最大的区别在于解决 hash 冲突。
HashMap 使用的拉链法,而 ThreadLocalMap 使用的线性探测法。
简单说:
当我们通过 int i = key.threadLocalHashCode & (len-1) 计算出 hash 值,如果出现冲突,顺序查看表中下一单元,直到找出一个空单元或查遍全表。
正因为采取的线性探测法解决冲突,所以在查找的时候,必须比较 key 值是否相等,否则顺序寻找下一个单元。
父子线程如何共享变量?
使用 InheritableThreadLocal 来解决,底层原理的话各位看官下去看看,下次再续。
输出结果:
@Python大星 | 文