线程局部变量的实现 ThreadLocal使用及场景介绍

软件发布|下载排行|最新软件

当前位置:首页IT学院IT技术

线程局部变量的实现 ThreadLocal使用及场景介绍

暮色妖娆丶   2023-02-03 我要评论

前言

回老家,实在太无聊,于是乎给自己整了一套台式机配置,总价 1W+,本以为机器到位后可以打打游戏,学学技术打发无聊的时光。但是我早已不是从前那个少年了,打 Dota 已经找不到大学时巅峰的自己,当年我一手 SF 真的是打遍天下无敌手......,和朋友打 LOL 又没有精力去学一个新的游戏,贼坑。。。

学技术又不想学,太懒了!!!于是乎,写文章吧...正好年后找工作用得上!今天我们来谈一谈 Java 中存储线程局部变量的类 ThreadLocal

ThreadLocal 介绍

This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its {@code get} or {@code set} method) has its own, independently initialized copy of the variable. {@code ThreadLocal} instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID)

上面这段是该类的注释:该提供线程局部变量。这些变量不同于它们正常的对应变量,因为每个通过 get()、set() 访问 ThreadLocal 变量的线程都有自己的、独立初始化的变量副本。ThreadLocal 实例通常是类中的私有静态字段,希望将状态与线程关联(例如,用户ID或事务ID)。

简单来说它的作用是作为一个数据结构,可以为每个线程分别存储他们私有的数据。我们可以暂时简单理解为下面这张图(实际上这个图是错的)

后面我们会详细介绍它的设计原理。

常用 API

方法作用
public ThreadLocal()实例化对象
ThreadLocal.withInitial(Supplier<? extends S> supplier )实例化对象并赋予它每个线程初始值
public void set(T value)设置当前线程绑定的变量
public T get()获取当前线程绑定的变量
public void remove()移除当前线程绑定的变量

ThreadLocal 使用场景

Spring 事务管理器

在 Spring 事务实现中,TransactionSynchronizationManager 类中声明了多个 ThreadLocal 类型的成员变量用以将事务执行过程中各种上下文信息绑定到当前线程,包括当前事务连接对象、是否可读、事务名称、隔离级别等

public abstract class TransactionSynchronizationManager {
   private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal<>("Transactional resources");
   private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations = new NamedThreadLocal<>("Transaction synchronizations");
   private static final ThreadLocal<String> currentTransactionName = new NamedThreadLocal<>("Current transaction name");
   private static final ThreadLocal<Boolean> currentTransactionReadOnly = new NamedThreadLocal<>("Current transaction read-only status");
   private static final ThreadLocal<Integer> currentTransactionIsolationLevel = new NamedThreadLocal<>("Current transaction isolation level");
   private static final ThreadLocal<Boolean> actualTransactionActive = new NamedThreadLocal<>("Actual transaction active");
   //......
  }

SpringMVC 存储上下文 Request 数据

RequestContextHolder 这个类是 SpringMVC 中提供的持有上下文 Request 的一个类,内部实现就是有两个 ThreadLocal 属性去存储请求对象数据。

private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
      new NamedThreadLocal<>("Request attributes");
private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
      new NamedInheritableThreadLocal<>("Request context");

以便于我们在业务代码中在没有 HttpServletRequest 对象的位置也可以通过 ThreadLocal 获取请求头等信息,比如我前面一篇关于 OpenFeign 向下游传递 header 的文章就用到了它。

PageHelper 分页的实现

之前流行的分页插件之一 PageHelper 其分页原理也是通过 ThreadLocal 实现,我们使用它进行分页时只需要在代码中调用静态方法

PageHelper.startPage(pageNum,pageSize);

接下来的第一条 SQL 就会自动进行分页,其实原理就是它将分页参数封装到一个 Page 对象中,然后将 Page 放进 ThreadLocal 中以达到 web 环境中多个线程互相分页不影响,后面就是都雷同的 SQL 拼接了。

存储用户身份信息

在很久之前我们用户登录信息的存储通常都是在 Session 中,后来大多是逐渐用 ThreadLocal 去代替从 Session 获取用户登录信息了。首先我们在用户每次请求需要授权的接口时,会让用户携带请求头 token ,后端在拦截器中拿到这个 tokenredis 查询用户信息,或者如果这个 tokenjwt 的话,直接解析它得到用户信息然后放进 ThreadLocal

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
    String header = request.getHeader("x-auth-token");
    //如果你的实现是 token 唯一字符串,从 Redis 拿用户信息
    User user = redisTemplate.opsForValue().get(header);
    //如果你的实现是 token 是jwt,那直接解析 jwt 拿到用户信息
    //.......
    if (user != null) {
        CurrentUser.set(user);
        return true;
    }
    return false;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    CurrentUser.clear();//请求结束之后不要忘记清除
}

CurrentUser

public class CurrentUser {
    public static final ThreadLocal<User> USER_THREAD_LOCAL = new ThreadLocal<>();
    public static void set(User user){
        USER_THREAD_LOCAL.set(user);
    }
    public static User get(){
        return USER_THREAD_LOCAL.get();
    }
    public static void clear(){
        USER_THREAD_LOCAL.remove();
    }
}

这样我们在任何地方只要使用 CurrentUser.get() 就能轻松获取到当前登录用户。

Copyright 2022 版权所有 软件发布 访问手机版

声明:所有软件和文章来自软件开发商或者作者 如有异议 请与本站联系 联系我们