【SpringBoot】ThreadLocal 的详解

一、ThreadLocal 简介

ThreadLocal 叫做线程变量,意思是 ThreadLocal 中填充的变量属于当前线程,该变量对其他线程而言是隔离的,也就是说该变量是当前线程独有的变量。ThreadLocal 为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。

ThreadLocal 变量,线程局部变量,同一个 ThreadLocal 所包含的对象,在不同的 Thread 中有不同的副本。这里有几点需要注意:

  • 因为每个 Thread 内有自己的实例副本,且该副本只能由当前 Thread 使用。这也是 ThreadLocal 命名的由来。
  • 既然每个 Thread 有自己的实例副本,且其它 Thread 不可访问,那就不存在多线程间共享的问题。

ThreadLocal 提供了线程本地的实例。它与普通变量的区别在于,每个使用该变量的线程都会初始化一个完全独立的实例副本。

ThreadLocal 变量通常被 private static 修饰。当一个线程结束时,它所使用的所有 ThreadLocal 相对的实例副本都可被回收。

下图可以增强理解:

二、ThreadLocal 与 Synchronized 的区别

ThreadLocal<T> 其实是与线程绑定的一个变量。ThreadLocal 和 Synchorized 都用于解决多线程并发访问。

但是 ThreadLocal 和 Synchorized 有本质的区别:

  1. Synchorized 用于线程间的数据共享,而 ThreadLocal 则用于线程间的数据隔离。
  2. Synchorized 是利用锁的机制,使变量或代码块在某一时刻只能被一个线程访问。而 ThreadLocal 为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。

而 Synchorized 却正好相反,它用于在多个线程间通信时能够获得数据共享。

总结:

一句话理解 ThreadLocal ,threadLocal 是作为当前线程中属性 ThreadLocalMap 集合中的某一个 Entry 的 key 值,Entry(threadlocal,value),虽然不同的线程之间 threadLocal 这个 key 值是一样,但是不同的线程所拥有的 ThreadLocalMap 是独一无二的,也就是不同的线程间同一个 ThreadLocal(key)对应存储的值(value)不一样,从而到达了线程间变量隔离的目的,但是在同一个线程中这个 value 变量地址是一样的。

三、ThreadLocal 的简单使用

java 复制代码
public class ThreadLocalTest {

    private static ThreadLocal<String> localVar = new ThreadLocal<String>();

    static void print(String str) {
        // 打印当前线程中本地内存中变量的值
        System.out.println(str + ":" + localVar.get());
        // 清除内存中的本地变量
        localVar.remove();
    }

    public static void main(String[] args) throws InterruptedException {
        new Thread(new Runnable() {
            @Override
            public void run() {
                ThreadLocalTest.localVar.set("xdclass_A");
                print("A");
                // 打印本地变量
                System.out.println("清楚后:" + localVar.get());
            }
        }, "A").start();
        Thread.sleep(1000);

        new Thread(new Runnable() {
            @Override
            public void run() {
                ThreadLocalTest.localVar.set("xdclass_B");
                print("B");
                // 打印本地变量
                System.out.println("清楚后:" + localVar.get());
            }
        }, "B").start();
    }
}


A:xdclass_A
清楚后:null
B:xdclass_B
清楚后:null

从这个示例中我们可以看到,两个线程分别获取了自己线程存放的变量,他们之间变量的获取并不会错乱。这个的理解也可以结合图,相信会有一个更深刻地理解。

四、ThreadLocal 常见使用场景

ThreadLocal 适用于如下两种场景

  1. 每个线程需要有自己单独的实例
  2. 实例需要在多个方法中共享,但不希望被多线程共享

对于第一点,每个线程拥有自己实例,实现它的方式很多。例如可以在线程内部构建一个单独的实例。ThreadLoca 可以以非常方便的形式满足该需求。

对于第二点,可以在满足第一点(每个线程有自己的实例)的条件下,通过方法间引用传递的形式实现。ThreadLocal 使得代码耦合度更低,且实现更优雅。

存储用户 userInfo 场景:
java 复制代码
@Slf4j
public class OnlineUserUtil {
    private final static ThreadLocal<UserInfo> threadLocal = new ThreadLocal<>();

    public static void set(UserInfo userInfo) {
        threadLocal.set(userInfo);
    }

    public static UserInfo get() {
        return threadLocal.get();
    }

    public static void remove() {
        threadLocal.remove();
    }

}
java 复制代码
@Slf4j
@Aspect
@Component
@Order(2)
public class TokenAuthenticationAspect {

    @Before(value = "@annotation(tokenAuthentication)")
    public void doBefore(JoinPoint pjp, TokenAuthentication tokenAuthentication) {

        // 校验代码

        log.info("验证成功,保存到threadLocal userInfo={}", userInfo);

        OnlineUserUtil.set(userInfo);

    }

    @AfterReturning(value = "@annotation(tokenAuthentication)")
    public void doAfter(TokenAuthentication tokenAuthentication) {
        OnlineUserUtil.remove();
    }

}

这样在方法中,都能用到 userInfo 这个对象。

五、如何正确的使用 ThreadLocal

  1. 将ThreadLocal变量定义成private static的,这样的话ThreadLocal的生命周期就更长,由于一直存在ThreadLocal的强引用,所以ThreadLocal也就不会被回收,也就能保证任何时候都能根据ThreadLocal的弱引用访问到Entry的value值,然后remove它,防止内存泄露。
  2. 每次使用完ThreadLocal,都调用它的remove()方法,清除数据。

六、参考文档

相关推荐
wellc1 天前
SpringBoot集成Flowable
java·spring boot·后端
IT_陈寒1 天前
React状态更新那点事儿,我掉坑里爬了半天
前端·人工智能·后端
Hui Baby1 天前
springAi+MCP三种
java
hsjcjh1 天前
【MySQL】C# 连接MySQL
java
敖正炀1 天前
LinkedBlockingDeque详解
java
wangyadong3171 天前
datagrip 链接mysql 报错
java
untE EADO1 天前
Tomcat的server.xml配置详解
xml·java·tomcat
ictI CABL1 天前
Tomcat 乱码问题彻底解决
java·tomcat
敖正炀1 天前
DelayQueue 详解
java
uzong1 天前
最新:阿里正式发布首款AI开发工具Meoo(秒悟),0门槛、一键部署上线
人工智能·后端