ThreadLocal原理解析
本篇将带大家了解ThreadLocal的使用方法,并且深度剖析其原理和作用,通过阅读源码的方式,进一步了解其内部原理
ThreadLocal
是 Java 提供的一个工具类,用于为每个线程维护一个独立的变量副本。每个线程可以访问自己专属的变量值,而其他线程无法访问或影响它,从而实现线程之间的隔离,当我们在同一线程中的不同层级之间,如果想要共享一些变量,而又不方便将变量作为参数传来传去时,ThreadLocal
就可以用来存放线程中的共享变量
比如在用户登录后做校验时,会把用户ID存放进去,以便在后面的业务中验证用户的身份,我们就会定义一个工具类:
public class UserHolder {
private static final ThreadLocal<UserDTO> tl = new ThreadLocal<>();
public static void saveUser(UserDTO user){
tl.set(user);
}
public static UserDTO getUser(){
return tl.get();
}
public static void removeUser(){
tl.remove();
}
}
ThreadLocal的实现原理
- 每个线程都有一个独立的ThreadLocal,他们互不影响
- 存储值时会获取当前线程对象Thread,Thread中有一个map结构,用来存储变量
- 这个map结构就是ThreadLocalMap,是一个哈希表,他是ThreadLocal的一个内部类
- ThreadLocalMap的key是ThreadLocal实例,value就是我们存进去的值
也就是说,用创建的ThreadLocal
实例调用set方法时,在方法中会先获取当``前线程的线程对象Thread
,在Thread
中维护了一个ThreadLocalMap
对象用来存放变量。而每个线程的线程对象是唯一的,因此该变量只在当先线程的任意位置可获取,而其他线程无法访问或影响它,从而实现线程之间的隔离
查看源码
打开ThreadLoca
l的源码,先来查看他的get
方法
get
方法:
public T get() {
return get(Thread.currentThread());
}
在get
方法调用了他的重载方法get(Thread t)
,获取当前线程对象Thread
作为参数:
private T get(Thread t) {
ThreadLocalMap map = getMap(t);
if (map != null) {
if (map == ThreadLocalMap.NOT_SUPPORTED) {
return initialValue();
} else {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T) e.value;
return result;
}
}
}
return setInitialValue(t);
}
这里getMap(Thread t)
方法从线程对象中获取其内部的 ThreadLocalMap
,每个线程都有自己的 ThreadLocalMap
,这个 Map 存储了所有 ThreadLocal 的变量,打开Thread
的源码就可以看到getMap方法
和这个ThreadLocalMap
:
ThreadLocalMap getMap(Thread t) {
return t.threadLocals; // 返回下面的ThreadLocalMap
}
/*
* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals;
接着会对这个线程对象里的map做逻辑判断:
- 如果map为空,则调用
setInitialValue()
返回一个默认值 - 如果不为空,则继续判断当前线程是否支持
ThreadLocal
,如果检测到当前线程的ThreadLocalMap
是 NOT_SUPPORTED,也就是不支持直接调用 initialValue()默认返回一个null
- 如果当前线程支持,则获取map中当前
ThreadLocal
的entry对象,在entry对象中获取value值返回
setInitialValue
方法:
private T setInitialValue(Thread t) {
// 调用子类实现的 initialValue 方法,获取初始值
T value = initialValue();
// 获取当前线程的 ThreadLocalMap
ThreadLocalMap map = getMap(t);
// 确保当前线程支持 ThreadLocal
assert map != ThreadLocalMap.NOT_SUPPORTED;
// 如果 map 已存在,设置初始值
if (map != null) {
map.set(this, value);
}
// 如果 map 为空,为当前线程创建一个新的 ThreadLocalMap 并存储初始值
else {
createMap(t, value);
}
// 如果当前 ThreadLocal 是 TerminatingThreadLocal 类型,注册清理逻辑
if (this instanceof TerminatingThreadLocal<?> ttl) {
TerminatingThreadLocal.register(ttl);
}
return value;
}
setInitialValue
方法的作用是 为当前线程的 ThreadLocal
变量设置初始值。当线程第一次访问 ThreadLocal
的值时,如果还没有为该线程存储任何值,ThreadLocal
会调用此方法完成初始化。这种机制确保每个线程有一个独立的初始值
initialValue
方法:
protected T initialValue() {
return null;
}
initialValue()
是一个可被子类重写的方法,用于指定线程的初始值,默认返回 null
那为什么当前线程不否支持ThreadLocal
时会调用该方法返回一个默认值呢?
其实这是提供了一种降级行为,确保即使线程不支持完整的 ThreadLocal
机制,程序也能提供默认值来维持基本功能,这种设计体现了对兼容性和灵活性的考虑
我们再查看他的set方法:
set
方法:
public void set(T value) {
set(Thread.currentThread(), value);
}
通扬也调用了重载方法传入了当前线程对象和要添加的值
private void set(Thread t, T value) {
ThreadLocalMap map = getMap(t);
if (map == ThreadLocalMap.NOT_SUPPORTED) {
throw new UnsupportedOperationException();
}
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
}
也是利用同样的getMap
方法获取到Thread
中的map
- 如果当前线程不支持
ThreadLocal
机制,则会抛出一个异常 - 如果map不为空,将传入的变量值set进集合中,key就是当前的
ThreadLoacl
实例对象 - 如果map是空,则调用
createMap
方法创建一个map集合并set初始的key和变量value
当我们使用完这个变量,就要从当前线程的 ThreadLocalMap 中移除当前 ThreadLocal 实例对应的变量,再让我们看一下remove
方法的实现
remove
方法:
public void remove() {
remove(Thread.currentThread());
}
获取当前线程对象,调用重载方法:
private void remove(Thread t) {
ThreadLocalMap m = getMap(t);
if (m != null && m != ThreadLocalMap.NOT_SUPPORTED) {
m.remove(this);
}
}
依然是调用我们最熟悉的getMap
方法获取map对象,判断它既不为空,同时当前线程支持ThreadLocal
机制,才能对其进行删除操作,执行map的remove方法将这个map删除,至此,对于ThreadLocal
的使用就结束了
通过对 ThreadLocal 原理和源码的解析,可以看出它的设计非常巧妙,能够高效地为每个线程维护独立的变量副本,从而实现线程之间的隔离性,在实际开发中,ThreadLocal 被广泛用于场景较为复杂的线程变量共享,尤其是跨层传递上下文信息的场景