现象
业务系统中,我们常常使用拦截器(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里方法返回的线程池
等等及框架实现的线程池类我们都必须检测到。