ThreadLocal原理解析

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就是我们存进去的值
set
Thread
ThreadLocalMap
...
key
value
....
key
value
ThreadLocal

也就是说,用创建的ThreadLocal实例调用set方法时,在方法中会先获取当``前线程的线程对象Thread,在Thread中维护了一个ThreadLocalMap对象用来存放变量。而每个线程的线程对象是唯一的,因此该变量只在当先线程的任意位置可获取,而其他线程无法访问或影响它,从而实现线程之间的隔离


查看源码

打开ThreadLocal的源码,先来查看他的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 被广泛用于场景较为复杂的线程变量共享,尤其是跨层传递上下文信息的场景

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值