摘要:本文学习了ThreadLocal的用法和原理。
环境
Windows 10 企业版 LTSC 21H2
Java 1.8
1 简介
ThreadLocal是一个本地线程副本变量工具类,主要用于将本地线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰。
2 用法
2.1 常用方法
2.1.1 设置
设置当前线程对应的局部变量的值。
源码:
1 | public void set(T value) { |
2.1.2 获取
返回当前线程对应的局部变量的值。
源码:
1 | public T get() { |
2.1.3 删除
删除当前线程对应的局部变量的值,目的是为了减少内存的占用。
当线程结束后,线程对应的局部变量将自动被回收,所以显式调用该方法并不是必须的操作,但它可以加快内存回收的速度。
如果在调用了remove()
方法后又调用了get()
方法,会进行重新初始化。
源码:
1 | public void remove() { |
2.1.3 初始化
返回当前线程对应的局部变量的初始值。
特点:
- 覆盖:该方法是
protected
方法,显然是为了让子类覆盖而设计的。 - 延迟:在线程首次调用
get()
方法或set()
方法时执行,并且仅执行一次。
源码:
1 | protected T initialValue() { |
2.2 使用场景
在一个线程中,需要共享某个资源,希望不管是在哪个类中使用该资源,都能保证该资源都是同一个,只会被创建一次,这就需要使用TheadLocal来实现。
在项目中有时会在一个线程内调用多个方法,并且在有的方法中需要传递上下文信息,比如用户信息等。
不使用TheadLocal,那就要给每一个方法增加参数,就会存在过渡传参问题,造成代码冗余:
1 | public class Demo { |
使用TheadLocal,那么只需要设置一次参数的绑定,在其他方法中就可以通过TheadLocal直接获取,不需要传参:
1 | public class Demo { |
3 原理
3.1 内部结构
在ThreadLocal类内部定义了ThreadLocalMap类,该类是一个类似HashMap的类,key存储对ThreadLocal实例的弱引用,value存储引用变量。
在Thread类内部有ThreadLocalMap类型的属性,通过Thread类内部的ThreadLocalMap对象对变量进行维护。
ThreadLocal不能解决线程之间的资源共享问题,ThreadLocal解决的是一个线程内部的资源共享问题。
3.2 线性探测
ThreadLocalMap结构非常简单,没有实现Map接口,其数据存储结构不是链表而是数组,解决哈希冲突的方式并非链表的拉链法方式,而是采用线性探测方式。
根据初始key的哈希值确定元素在table数组中的位置,如果已被占用,检查通过算法得到的下一个位置,如果检查到末尾没有空位置,则从开头继续检查,直至找到空位置。
如果在ThreadLocalMap中存储了大量的ThreadLocal会导致出现聚集,严重影响解决哈希冲突的性能,建议使用Map作为变量管理不同类型的参数。
3.4 数据共享
如果在当前线程创建了子线程,在子线程中想要获取父线程中的变量,就不能使用ThreadLocal类,需要使用InheritableThreadLocal类。
InheritableThreadLocal类继承于ThreadLocal类,会自动为子线程复制继承自父线程的本地变量。
在创建子线程时,子线程会接收所有可继承的线程本地变量的初始值,当必须将本地线程变量自动传送给所有创建的子线程时,应尽可能的使用InheritableThreadLocal,而非ThreadLocal。
示例:
1 | public static void main(String[] args) { |
4 内存泄漏
4.1 定义
虚拟机会在达到触发条件时,对内存中不被使用的对象和变量进行回收,以便合理利用内存空间。
当不被使用的对象或者变量占用的内存不能被回收时,就会产生内存泄漏。
由ThreadLocal引发的内存泄露分为两种情况:
- 由ThreadLocalMap中的key引起的,也就是ThreadLocal对象内存泄露。
- 由ThreadLocalMap中的value引起的,也就是ThreadLocal对象绑定的对象内存泄露。
4.2 引用类型
根据引用类型可以分为强软弱虚四种:
强引用(StrongReference):不会被垃圾回收器回收,即使以后也不会用到。
软引用(SoftReference):比强引用弱,当系统内存不足时才会被回收。通常用在对内存敏感的程序中,比如高速缓存。
弱引用(WeakReference):比软引用弱,生命周期更短,只要发生了垃圾回收,不管内存空间是否足够都会被回收。
虚引用(PhantomReference):最弱,在任何时候都有可能被垃圾回收器回收。通常配和引用队列联合使用,在被回收前能够收到系统通知。
当需要操作共享变量时,需要先声明ThreadLocal对象,然后通过ThreadLocal对象的方法操作共享变量,这个操作实际上是通过Thread类里的ThreadLocalMap集合实现的。
4.3 解决办法
4.3.1 key引起的内存泄漏
在ThreadLocalMap集合中,key存储了ThreadLocal对象的弱引用,这么做是为了避免强引用导致的内存泄漏:
- 如果在ThreadLocalMap集合中使用强引用,加上声明ThreadLocal对象使用的默认是强引用,此时ThreadLocal对象就同时拥有两个强引用,即使将声明ThreadLocal的变量置空后,因为存在另一个强引用,就会导致ThreadLocal对象不能被回收,进而导致内存泄露。
- 如果在ThreadLocalMap集合中使用弱引用,此时ThreadLocal对象就只有一个强引用,在将声明ThreadLocal对象的变量置空后,没有强引用指向ThreadLocal对象,能够保证在ThreadLocal对象在下一次垃圾回收时被回收。
4.3.2 value引起的内存泄漏
在ThreadLocalMap集合中,value存储了变量的强引用。
当声明ThreadLocal对象的变量置空后,因为弱引用导致出现了key为null但value不为null的键值对,于是value存储的变量无法被获取也不能被回收,产生内存泄漏。
解决办法是及时调用remove()
方法将用过的value对象移除。
条