解决ThreadLocal获取不到值大坑

2023-05-19 11:05:53 获取 大坑 不到

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获取不到值解决的资料请关注其它相关文章!

相关文章