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