工作面试中经常遇到ThreadLocal,但是很多同学并不了解ThreadLocal实现原理,到底为什么会发生内存泄漏也是一知半解?今天一灯带你深入剖析ThreadLocal源码,总结ThreadLocal使用规范,解析ThreadLocal高频面试题。
1. ThreadLocal是什么
ThreadLocal是线程本地变量,就是线程的私有变量,不同线程之间相互隔离,无法共享,相当于每个线程拷贝了一份变量的副本。
目的就是在多线程环境中,无需加锁,也能保证数据的安全性。
2. ThreadLocal的使用
/**
* @author 一灯架构
* @apiNote ThreadLocal示例
**/
public class ThreadLocalDemo {
// 1. 创建ThreadLocal
static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
// 2. 给ThreadLocal赋值
threadLocal.set("关注公众号:一灯架构");
// 3. 从ThreadLocal中取值
String result = threadLocal.get();
System.out.println(result); // 输出 关注公众号:一灯架构
// 4. 删除ThreadLocal中的数据
threadLocal.remove();
System.out.println(threadLocal.get()); // 输出null
}
}
ThreadLocal的用法非常简单,创建ThreadLocal的时候指定泛型类型,然后就是赋值、取值、删除值的操作。
不同线程之间,ThreadLocal数据是隔离的,测试一下:
/**
* @author 一灯架构
* @apiNote ThreadLocal示例
**/
public class ThreadLocalDemo {
// 1. 创建ThreadLocal
static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
IntStream.range(0, 5).forEach(i -> {
// 创建5个线程,分别给threadLocal赋值、取值
new Thread(() -> {
// 2. 给ThreadLocal赋值
threadLocal.set(i);
// 3. 从ThreadLocal中取值
System.out.println(Thread.currentThread().getName()
+ "," + threadLocal.get());
}).start();
});
}
}
输出结果:
Thread-2,2
Thread-4,4
Thread-1,1
Thread-0,0
Thread-3,3
可以看出不同线程之间的ThreadLocal数据相互隔离,互不影响,这样的实现效果有哪些应用场景呢?
3. ThreadLocal应用场景
ThreadLocal的应用场景主要分为两类:
-
避免对象在方法之间层层传递,打破层次间约束。
比如用户信息,在很多地方都需要用到,层层往下传递,比较麻烦。这时候就可以把用户信息放到ThreadLocal中,需要的地方可以直接使用。
-
拷贝对象副本,减少初始化操作,并保证数据安全。
比如数据库连接、Spring事务管理、SimpleDataFormat格式化日期,都是使用的ThreadLocal,即避免每个线程都初始化一个对象,又保证了多线程下的数据安全。
使用ThreadLocal保证SimpleDataFormat格式化日期的线程安全,代码类似下面这样:
/**
* @author 一灯架构
* @apiNote ThreadLocal示例
**/
public class ThreadLocalDemo {
// 1. 创建ThreadLocal
static ThreadLocal<SimpleDateFormat> threadLocal =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
public static void main(String[] args) {
IntStream.range(0, 5).forEach(i -> {
// 创建5个线程,分别从threadLocal取出SimpleDateFormat,然后格式化日期
new Thread(() -> {
try {
System.out.println(threadLocal.get().parse("2022-11-11 00:00:00"));
} catch (ParseException e) {
throw new RuntimeException(e);
}
}).start();
});
}
}
4. ThreadLocal实现原理
ThreadLocal底层使用ThreadLocalMap存储数据,而ThreadLocalMap内部是一个数组,数组里面存储的是Entry对象,Entry对象里面使用key-value存储数据,key是ThreadLocal实例对象本身,value是ThreadLocal的泛型对象值。
4.1 ThreadLocalMap源码
static class ThreadLocalMap {
// Entry对象,WeakReference是弱引用,当没有引用指向时,会被GC回收
static class Entry extends WeakReference<ThreadLocal<?>> {
// ThreadLocal泛型对象值
Object value;
// 构造方法,传参是key-value
// key是ThreadLocal对象实例,value是ThreadLocal泛型对象值
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
// Entry数组,用来存储ThreadLocal数据
private Entry[] table;
// 数组的默认容量大小
private static final int INITIAL_CAPACITY = 16;
// 扩容的阈值,默认是数组大小的三分之二
private int threshold;
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
}
4.2 set方法源码
// 给ThreadLocal设值
public void set(T value) {
// 获取当前线程对象
Thread t = Thread.currentThread();
// 获取此线程对象中的ThreadLocalMap对象
Thre