业务代码在线程池中乱使用java.lang.ThreadLocal变量,导致信息传递丢失的故障

2023-06-19 15:18:21 浏览数 (1)


现象


业务系统中,我们常常使用拦截器(spring中filter、HandlerInterceptor、aop)拦截登录校验,然后把登录用户的信息比如用户id放到一个

ThreadLocal的变量里。

隐蔽潜在故障


ThreadLocal变量在登录拦截器里设置的用户信息,在我的编码规范里必须控制在spring中的controller层,因为默认,controller层使用的线程与spring容器底层线程为同一个线程。

如果我们业务使用了线程池,而线程线提交的任务中又使用到了此ThreadLocal变量,会造成信息的丢失,导致故障的发生

示例:

代码语言:javascript复制
package com.renzhikeji.annotation;

import lombok.Data;

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

/**
 * @author 认知科技技术团队
 * 微信公众号:认知科技技术团队
 */
public class ThreadLocalDemo {
    public static void main(String[] args) throws InterruptedException {
        LoginUserInfos.setUserId(88L);
        System.out.println(LoginUserInfos.getUserId());

        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(LoginUserInfos.getUserId());
            }
        });

        thread.start();
        thread.join();
    }

    @Data
    public static final class LoginUserInfos {
        private static final String USER_ID = "userId";
        private static final ThreadLocal<Map<String, Object>> THREAD_LOCAL = new ThreadLocal<>() {
            @Override
            protected Map<String, Object> initialValue() {
                return new HashMap<>();
            }
        };

        public static void setUserId(Long userId) {
            Map<String, Object> map = THREAD_LOCAL.get();
            map.put(USER_ID, userId);
        }

        public static Long getUserId() {
            Object userId = THREAD_LOCAL.get().get(USER_ID);
            //不设置默认值,还可能利用NPE异常发现问题喔
            return (Long) Optional.ofNullable(userId).orElse(0L);
        }
    }
}

源码 https://gitee.com/whoamiy/demo/blame/threadlocal/java-demo/src/main/java/com/renzhikeji/annotation/ThreadLocalDemo.java

第21行输出的肯定是0值,没有获取到真正的用户信息。

小结


java中的ThreadLocal使用,在项目中及框架中的出现很普遍。看过ThreadLocal源码的同学都知道这个是与线程绑定的变量,很容易造成内存泄露,但也会造成信息传递的丢失

我们可以使用maven规则插件,检测ThreadLocal变量及包装此变量的类,在代码中的使用限制。

代码语言:javascript复制
Thread、
ThreadPoolExecutor、
ScheduledThreadPoolExecutor、
java.util.stream.BaseStream#parallel、
java.util.Collection#parallelStream、
java.util.concurrent.CompletableFuture#supplyAsync(java.util.function.Supplier<U>, java.util.concurrent.Executor)、
java.util.concurrent.CompletableFuture#supplyAsync(java.util.function.Supplier<U>)、
ForkJoinPool、
java.util.concurrent.Executors里方法返回的线程池

等等及框架实现的线程池类我们都必须检测到。

0 人点赞