ThreadLocal的介绍和使用记录

一、优势特点

a、ThreadLocal中包含的对象,可以在不同的Thread中保持不同的副本,且该副本只能当前Thread使用
b、解决了多线程之间的共享问题
c、使用于每个线程都需要自己独立的实例,并且该实例需要在多个方法中使用,也即变量在线程见隔离,而在方法或者类间共享的场景。
d、达到线程安全的目的
e、不需要加锁,执行效率高
f、更加节省内存,节省开销
g、免去传参的繁琐,降低代码耦合度
......

二、主要使用场景

在每个线程内需要保存全局变量(例如在拦截器中获取用户信息),让不同方法直接使用,避免参数传递的麻烦

三、使用事例

3.1 创建UserThreadLocal.java

public class UserThreadLocal {
    private static ThreadLocal<UserInfoPo> threadLocal = new ThreadLocal<>();

    /**
     * 设置线程数据UserInfoPo
     * @param UserInfoPo 缓存数据
     */
    public static void set(UserInfoPo user){
        threadLocal.set(user);
    }

    /**
     * 获取线程数据
     * @return UserInfoPo
     */
    public static UserInfoPo get(){
        return threadLocal.get();
    }

    /**
     * 删除线程数据
     */
    public static void remove(){
        threadLocal.remove();
    }
}

3.2 实现HandlerInterceptor拦截器

@Slf4j
@Component
public class UserInterceptor implements HandlerInterceptor {

    private final RedisTemplate redisTemplate;

    @Autowired
    public UserInterceptor(RedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String userToken = request.getHeader("token");
        if (StringUtils.isEmpty(userToken)) {
            throw new UserException(ResultCode.USER_NOT_EXCEPTION);
            return false;
        }
        Object userInfoPo = redisTemplate.opsForValue().get(userToken);
        if (StringUtils.isEmpty(userInfoPo)) {
             throw new UserException(ResultCode.USER_NOT_EXCEPTION);
             return false;
        }
        UserThreadLocal.set((UserInfoPo) userInfoPo);
        return true;
    }
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
        UserThreadLocal.remove();
    }

}

3.3 InterceptorMvc.java的配置

@Configuration
public class InterceptorMvc implements WebMvcConfigurer {

    private final UserInterceptor userInterceptor;

    @Autowired
    public InterceptorMvc(UserInterceptor userInterceptor) {
        this.userInterceptor = userInterceptor;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
         // 定义路由拦截数组
  	     String[] userPathPattern = {"/test/**"};
  	     // 除去特殊拦截路径
         String[] userExcludePathPatterns = {"/test/testOne"};      
         registry.addInterceptor(userInterceptor).addPathPatterns(userPathPattern).excludePathPatterns(userExcludePathPatterns);
        
    }
}

3.4 controller层相关拦截接口直接使用获取对象

UserInfoPo userInfoPo = UserThreadLocal.get();

四、ThreadLocal注意点

4.1 内存泄漏

	某个对象不会再被使用,但是该对象的内存却无法被收回
	强引用:当内存不足时触发GC,宁愿抛出OOM也不会回收强引用的内存
	弱引用:触发GC后便会回收弱引用的内存
	原因: 线程一直再执行中,强引用链下的value值始终无法被回收导致内存溢出
	参考链接: https://zhuanlan.zhihu.com/p/128102523

参考地址:http://www.threadlocal.cn/#1

### Java `ThreadLocal` 工作原理 `ThreadLocal` 是一种特殊的变量容器,允许每个线程拥有该变量的一个独立副本。这意味着不同线程之间无法相互干扰彼此持有的 `ThreadLocal` 变量实例[^1]。 具体来说,在 JVM 中每一个线程都维护着一个名为 `ThreadLocalMap` 的哈希表结构来保存这些特定于当前线程的数据项。每当调用 `set()` 方法向某个 `ThreadLocal` 对象赋值时,实际上是在对应的线程内部创建了一个键值对记录;当通过 `get()` 获取值的时候,则是从这个映射关系里查找并返回相应的对象引用[^2]。 对于继承自父级线程属性的情况(即 `InheritableThreadLocal`),JVM 还会复制一份来自父进程的相关配置给新启动的孩子们使用[^3]。 ```java public class ThreadLocalExample { private static final ThreadLocal<Integer> threadLocalValue = ThreadLocal.withInitial(() -> 0); public void increment() { Integer value = threadLocalValue.get(); threadLocalValue.set(value + 1); } public int getValue() { return threadLocalValue.get(); } } ``` 这段代码展示了如何定义一个初始值为零 (`withInitial`) 的整数类型的 `ThreadLocal` 实例,并实现了两个方法用于操作其上的数值——增加计数器(`increment`) 读取当前值(`getValue`)。 ### 应用场景 #### 数据库连接池管理 在一个典型的Web应用服务器环境中,多个请求可能并发执行数据库查询任务。为了提高性能资源利用率,通常采用连接池技术预先分配一定数量的数据库链接供后续重用。此时就可以利用 `ThreadLocal` 来确保每次HTTP请求处理过程中所使用的都是同一个持久化的Session对象,从而简化事务管理错误恢复逻辑[^4]。 #### 用户上下文传递 假设存在一个多租户SaaS平台架构下的微服务组件通信链路,其中涉及到跨层传播认证信息的需求。借助 `ThreadLocal` ,可以在进入业务流程之初就将登录用户的唯一标识符存入内存空间内,之后无论经过多少次函数调用跳转都不会丢失这条重要线索,直到整个交易结束为止自动清除掉临时缓存的内容。 #### 并发计算中的状态保持 考虑这样一个例子:有一个复杂的算法需要分成若干个小部分由不同的工作单元协同完成。如果希望各个子任务能够共享某些中间结果而不必担心同步竞争条件带来的麻烦的话,那么完全可以把它们封装成静态字段形式并通过 `ThreadLocal` 方式来进行分发控制。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值