ThreadLocal与InheritableThreadLocal
背景
你是否还不了解threadLocal是什么?
你是否在为了多线程运行业务逻辑时,需要手动构造线程上下文传递到子线程,感觉到代码冗余?
那么是时候学习一下threadLocal了
ThreadLocal
threadLocal 线程本地变量,也叫做线程本地存储,他的作用是提供线程内部的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度
ThreadLocal
中填充的的是当前线程的变量,该变量对其他线程而言是封闭且隔离的,ThreadLocal
为变量在每个线程中创建了一个副本,这样每个线程都可以访问自己内部的副本变量。
ThreadMap
-
每个线程中都有一个自己的ThreadLocalMap类对象,可以将线程自己的对象保存到其中,各管各的互不干扰,线程可以正确的访问到自己的对象
-
将一个公用的ThreadLocal静态实例作为key,将不同对象引用保存到不同线程的ThreadLocalMap中,然后在线程执行的各处通过这个静态ThreadLocal实例的get()方法取得自己线程保存的这个对象,避免了将这个对象作为参数传递的麻烦。
-
ThreadLocalMap其实就是线程里面的一个属性,他在Thread中的定义
ThreadLocal.ThreadLocalMap threadLocals = null;
-
调用ThreadLocal的get()方法时,实际上就是从ThreadLocalMap获取值,key是ThreadLocal对象
-
ThreadLocal本身并不存储值,它只是作为一个key来让线程从ThreadLocalMap获取value。
使用场景
最常见的ThreadLocal使用场景就是用来解决 数据库链接、Session管理、用户登录信息记录…
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.apache.commons.lang3.RandomUtils;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadLocalDemo {
public static void main(String[] args) {
var workQueue = new LinkedBlockingQueue<Runnable>();
var threadFactory = Executors.defaultThreadFactory();
var threadPool = new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors() * 2,
1000,
10,
TimeUnit.SECONDS,
workQueue,
threadFactory,
new ThreadPoolExecutor.AbortPolicy());
var threadLocal = new ThreadLocal<User>();
threadLocal.set(User.of("主线程"));
System.out.println(threadLocal.get());
for (int i = 0; i < 10; i++) {
threadPool.execute(new Thread(() -> {
threadLocal.set(User.of());
System.out.println(threadLocal.get());
}));
}
}
}
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
class User {
private String name;
private long randomNumber;
private String desc;
public static User of() {
return new User(Thread.currentThread().getName(), RandomUtils.nextLong(), null);
}
public static User of(String desc) {
return new User(Thread.currentThread().getName(), RandomUtils.nextLong(), desc);
}
}
这个代码演示了 线程之间的threadLocal 是互相隔离的,子线程也并不继承父线程的数据,父子线程之间的数据无法传递
源码
可以看见,调用threadLocal的set方法,使用当前线程作为k,去获取threadLocalMap
而get也是同样
都是获取的当前线程的map,无法获取到其他线程的数据
InheritableThreadLocal
- 简介:ThreadLocal的升级版,jdk原生,实现了父子线程之间的数据传递。
- 原理:重新开辟一块空间(InheritableThreadLocal)用于存储父子线程共用的数据隔离空间。重写了ThreadLocal的三个方法,当创建新线程的时候,将父线程的ThreadLocal值拷贝到子线程。
- 不足:无法完成线程池间的数据传递
在上面的截图中,thread里面存储了threadLocals,但是在他的下面还有一个成员变量就是inheritableThreadLocal
还是上面的代码,我们做个小改动
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.apache.commons.lang3.RandomUtils;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadLocalDemo {
public static void main(String[] args) {
var workQueue = new LinkedBlockingQueue<Runnable>();
var threadFactory = Executors.defaultThreadFactory();
var threadPool = new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors() * 2,
1000,
10,
TimeUnit.SECONDS,
workQueue,
threadFactory,
new ThreadPoolExecutor.AbortPolicy());
// var threadLocal = new ThreadLocal<User>();
var threadLocal = new InheritableThreadLocal<User>();
threadLocal.set(User.of("主线程"));
System.out.println(threadLocal.get());
for (int i = 0; i < 10; i++) {
threadPool.execute(new Thread(() -> {
System.out.println("原始数据:" + Thread.currentThread().getName() + threadLocal.get().getDesc());
threadLocal.set(User.of());
System.out.println(threadLocal.get());
}));
}
}
}
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
class User {
private String name;
private long randomNumber;
private String desc;
public static User of() {
return new User(Thread.currentThread().getName(), RandomUtils.nextLong(), null);
}
public static User of(String desc) {
return new User(Thread.currentThread().getName(), RandomUtils.nextLong(), desc);
}
}
可以看见单个线程池中的子线程是把父线程的数据给带出来了,并修改
源码
看源码的入口可以从threaed源码开始,找到init方法,
里面会判断parent对应threadLocal是否不为空,不为空则会创建共享变量的副本
可以看见inheritableThread的childValue重写方法被调用
InheritableThreadLocal类通过重写方法在调用get、set方法的时候会调用重写的方法,调用方法时会对线程的inheritableThreadLocals变量进行初始化,在对子线程进行初始化的时候会将子线程的inheritableThreadLocals变量赋值为父线程的inheritableThreadLocals变量值,这样就实现了子线程继承父线程
但是需要注意,不合理的使用会导致内存泄漏
- threadLocalMap 中的entry 是一个弱引用对象
其构造函数super调用了父类的构造函数,传递了k变量,即ThreadLocal对象的引用,也就是说Map集合中存储的是ThreadLocal的弱引用,如果当前线程一直存在且没有调用ThreadLocal的remove方法,那么ThreadLocal会在下次GC的时候将弱引用对象回收,这样就会造成ThreadLocalMap对象中的Entry对象的key为null,但是value值还是存在强引用关系,这样就会造成内存泄漏问题
- InheritableThreadLocal类子线程使用线程池更改存储变量值不变问题
拓展-TransmittableThreadLocal
阿里开源的TransmittableThreadLocal的正确使用姿势_com.alibaba.ttl.transmittablethreadlocal-CSDN博客