解决ThreadLocal获取不到值大坑

 更新时间:2023年05月08日 11:09:53   作者:我是小趴菜  
这篇文章主要介绍了解决ThreadLocal获取不到值大坑

1:问题起因

今天项目上测试环境,再给领导演示的时候出现了bug,很尴尬。于是我跟前端同学通过模拟请求,最后发现在调一个接口的时候返回了一个 token为空 的错误。

但是前端同学说传了token了,那为什么还会报token为空的错误呢

我们项目使用的JWT生成用户token,每次请求都要经过拦截器校验。因为在请求的时候我们经常需要用到当前用户登录的ID,所以我们使用到了ThhreadLocal这个工具类。

Map<String, Claim> claimMap = JwtUtil.verifyToken(headerToken);
if (null == claimMap) {
    throw new GlobalException(ResponseEnums.TOKEN_INVALID_ERROR);
}
//将参数放入上下文中
Map<String, Object> result = new HashMap<>();
Set<Map.Entry<String, Claim>> entrySet = claimMap.entrySet();
for (Map.Entry<String, Claim> claimEntry : entrySet) {
    result.put(claimEntry.getKey(), claimEntry.getValue().asString());
}
//将用户ID存到ThreadLocal中,以便后续的获取使用
ThreadLocalUtil.getInstance().setContext(result);

这里也贴上ThreadLocalUtil工具类代码

@Slf4j
public class ThreadLocalUtil {
    private static final ThreadLocal<Map<String, Object>> CONTEXT = new ThreadLocal<>();
    private ThreadLocalUtil() {}
    public static ThreadLocalUtil getInstance() {
        return SingletonHolder.INSTANCE;
    }
    private static class SingletonHolder {
        private static final ThreadLocalUtil INSTANCE = new ThreadLocalUtil();
    }
    public void setContext(Map<String, Object> map) {
        CONTEXT.set(map);
    }
    public static Map<String, Object> getContext() {
        return CONTEXT.get();
    }
    public void clear() {
        CONTEXT.remove();
    }
    public static Integer getUserId() {
        Map<String, Object> context = getContext();
        if(context == null || !context.containsKey(JwtUtil.USER_ID)) {
            throw new GlobalException(ResponseEnums.TOKEN_IS_NULL_ERROR);
        }
        return Integer.parseInt(String.valueOf(context.get(JwtUtil.USER_ID)));
    }
}

2:问题复现

我们写一个简单的测试

public static void main(String[] args) {
    HashMap<String,Object> map = new HashMap<>();
    map.put(JwtUtil.USER_ID,"1");
    ThreadLocalUtil.getInstance().setContext(map);
    System.out.println(ThreadLocalUtil.getUserId());
}

可以看到可以拿到我们设置的值。

但是如果将ThreadLocal跟Java8的Stream一起配合使用呢?

public static void main(String[] args) {
    HashMap<String,Object> map = new HashMap<>();
    map.put(JwtUtil.USER_ID,"1");
    ThreadLocalUtil.getInstance().setContext(map);
    List<Integer> list = new ArrayList<>();
    list.add(1);
    list.add(2);
    list.add(3);
    //ThreadLocal获取值放在stream里面执行
    list.parallelStream().filter(x -> x.equals(ThreadLocalUtil.getUserId())).collect(Collectors.toList());
}

我们的问题复现了,报了token为空的错误。但是这还是随机会出现的情况,并不是每次都会出现。所以导致我们在调试的时候并没有出现这个问题

3:分析问题

咋一看并不能知道为什么会这样,所以我在获取用户id的打印了一下日志

看出问题了吧,竟然有三个线程来获取,因为我们设置值的线程就是main线程,所以前面二个线程获取到的值就是空的,所以就抛出了异常

所以现在只需要知道这个 ForkJoinPool是在哪就好了,最终在翻看源码,找到原来就是在jdk8的Stream里面。

这是为什么呢?因为Jdk8的Stream底层使用了ForkJoinPool线程池,这就导致当我们调用 ThreadLocalUtil.getUserId()的时候,是直接提交到了ForkJoinPool线程池中去了,这时候就会有其它线程去调用这个方法,所以就拿不到值了

4:如何解决

解决办法就很简单了,只需要把ThreadLocalUtil.getUserId()单独拿出来执行就可以了

public static void main(String[] args) {
    HashMap<String,Object> map = new HashMap<>();
    map.put(JwtUtil.USER_ID,"1");
    ThreadLocalUtil.getInstance().setContext(map);
    List<Integer> list = new ArrayList<>();
    list.add(1);
    list.add(2);
    list.add(3);
    //单独拿出来执行
    Integer userId = ThreadLocalUtil.getUserId();
    list.parallelStream().filter(x -> x.equals(userId)).collect(Collectors.toList());
}

所以当你项目使用到了ThreadLocal的时候,切记要单独使用,否则指不定就出现跟我一样的问题了

以上就是解决ThreadLocal获取不到值大坑的详细内容,更多关于ThreadLocal获取不到值解决的资料请关注脚本之家其它相关文章!

相关文章

  • Spring bean的实例化和IOC依赖注入详解

    Spring bean的实例化和IOC依赖注入详解

    这篇文章主要介绍了Spring bean的实例化和IOC依赖注入详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2019-03-03
  • SpringBoot之spring.factories的使用方式

    SpringBoot之spring.factories的使用方式

    这篇文章主要介绍了SpringBoot之spring.factories的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-01-01
  • SpringBoot线程池ThreadPoolTaskExecutor异步处理百万级数据

    SpringBoot线程池ThreadPoolTaskExecutor异步处理百万级数据

    本文主要介绍了SpringBoot线程池ThreadPoolTaskExecutor异步处理百万级数据,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-03-03
  • Java编写简单猜数游戏

    Java编写简单猜数游戏

    这篇文章主要为大家详细介绍了Java编写简单猜数游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-01-01
  • java实现接口的典型案例

    java实现接口的典型案例

    下面小编就为大家带来一篇java实现接口的典型案例。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-09-09
  • ReentrantReadWriteLock 读写锁分析总结

    ReentrantReadWriteLock 读写锁分析总结

    这篇文章主要介绍了ReentrantReadWriteLock 读写锁分析总结,ReentranReadWriteLock中有两把锁,一把读锁,一把写锁,关于这两把锁的介绍,需要的小伙伴可以参考一下
    2022-05-05
  • Mybatis-Plus中的条件参数使用

    Mybatis-Plus中的条件参数使用

    这篇文章主要介绍了Mybatis-Plus中的条件参数使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-05-05
  • Java模板引擎Thymeleaf基本语法详解

    Java模板引擎Thymeleaf基本语法详解

    当开发Web应用程序时,我们通常需要使用模板引擎来构建和呈现动态内容,Thymeleaf是一个功能强大的Java模板引擎,它提供了丰富的表达式和标签,使得数据绑定、条件判断、循环迭代等操作变得轻松而灵活,本文就简单的给大家介绍一下Thymeleaf基本语法
    2023-08-08
  • Spring 静态变量/构造函数注入失败的解决方案

    Spring 静态变量/构造函数注入失败的解决方案

    我们经常会遇到一下问题:Spring对静态变量的注入为空、在构造函数中使用Spring容器中的Bean对象,得到的结果为空。不要担心,本文将为大家介绍如何解决这些问题,跟随小编来看看吧
    2021-11-11
  • Mybatis plugin的使用及原理示例解析

    Mybatis plugin的使用及原理示例解析

    这篇文章主要为大家介绍了 Mybatis plugin的使用及原理示例解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-09-09

最新评论