写博客的目的在于自我省,自我总结,自我梳理
烦恼:
你是否有如下烦恼:你的系统日志打印非常乱,每次排查问题需要根据业务id去查询每个对应服务中这次请求所对应的日志,在单线程情况下,还能按照业务的调用顺序去排查,但是在多线程情况下,日志可能存在乱序,排查起来非常难受。
解决:
ThreadLocal的用法,除了用于SimpleDateFormate等之外,还有更好的应用场景:在多线程情况的日志跟踪,同一线程或者一次事务的日志打印,将事务的日志保存在线程局部变量当中,在一次API请求的全链路下,均能全局的获取到这个日志的序号,无需侵入你的业务参数中层层传递。
思路:
首先你可能会想到用static的全局变量去记录日志序号,也能达到效果,但是多线程情况下同时对同一个静态变量操作时,无法确保每个线程取出的值是自己放的值。而ThreadLocal可以把变量绑定到到某一线程上,形成多线程都有自己的变量副本。
ThreadLocal的一点小分析:
先看看ThradLocal的源码:
ThradLocalMap的key为弱引用,JVM在GC时会回收,造成key=null,value=ThradLocalMap,造成内存溢出。
SpringAOP
此处用到了基于切面的几个注解,在SpringMVC的RequestMapping注解上进行拦截。
@Pointcut:切入点
@Before:前置通知
@After:后置通知
/**
* 切面生成处理流程序列号
**/
@Aspect
@Component
@Slf4j
public class RestSeqNumAspect {
@Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping)")
public void requestMapping() {
}
@Before("requestMapping()")
public void doBefore(JoinPoint joinPoint) throws Exception {
ThreadLocalValueHolder.set(joinPoint.getSignature().getName()+"#"+System.currentTimeMillis()+"#"+Thread.currentThread().getId());
log.info(" rest request[{}] begin------------------",""+ThreadLocalValueHolder.get());
}
@After("requestMapping()")
public void doAfter(JoinPoint joinPoint) throws Exception {
log.info(" rest request[{}]end------------------",""+ThreadLocalValueHolder.get());
ThreadLocalValueHolder.set(null);
}
}
在doAfter中需要将对象置为null,释放内存
ThreadLocal类的声明,调用其set(),get()方法就能获取到当前线程的全局变量
public class ThreadLocalValueHolder {
public static final ThreadLocal<Object> THREAD_LOCAL = new ThreadLocal();
public ThreadLocalValueHolder() {
}
public static <T> void set(T value) {
THREAD_LOCAL.set(value);
}
public static <T> T get() {
return THREAD_LOCAL.get();
}
}
效果展示:
如图所示,我们能清楚的知道相同的日志序号是来自同一个请求,笔者只在接口的入口和出口处打印了序号,当然可以在每条日志中调用ThreadLocalValueHolder.get()进行打印,获取到的肯定是当前线程生成的序号。
总结:
使用ThreadLocal还能用于全局的用户信息,用户session等,把这些通用的变量或者对象从业务实体中抽离出来,减少参数的层层传递,以空间换时间,解决并发下对临界资源的访问。