【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()方法,清除数据。

六、参考文档

相关推荐
源代码•宸6 小时前
大厂技术岗面试之谈薪资
经验分享·后端·面试·职场和发展·golang·大厂·职级水平的薪资
Anastasiozzzz6 小时前
Java Lambda 揭秘:从匿名内部类到底层原理的深度解析
java·开发语言
骇客野人6 小时前
通过脚本推送Docker镜像
java·docker·容器
韩立学长6 小时前
基于Springboot泉州旅游攻略平台d5h5zz02(程序、源码、数据库、调试部署方案及开发环境)系统界面展示及获取方式置于文档末尾,可供参考。
数据库·spring boot·旅游
铁蛋AI编程实战6 小时前
通义千问 3.5 Turbo GGUF 量化版本地部署教程:4G 显存即可运行,数据永不泄露
java·人工智能·python
晚霞的不甘6 小时前
CANN 编译器深度解析:UB、L1 与 Global Memory 的协同调度机制
java·后端·spring·架构·音视频
SunnyDays10117 小时前
使用 Java 冻结 Excel 行和列:完整指南
java·冻结excel行和列
喵叔哟7 小时前
06-ASPNETCore-WebAPI开发
服务器·后端·c#
摇滚侠7 小时前
在 SpringBoot 项目中,开发工具使用 IDEA,.idea 目录下的文件需要提交吗
java·spring boot·intellij-idea
云姜.7 小时前
java多态
java·开发语言·c++