线程池使用InheritableThreadLocal踩坑总结

1、缘起

某天测试环境更新后,有小伙伴反应页面会随机性的发生请求参数为空的状况(request.getParamter为空),可是前端的参数是传了的,并且不能稳定重现,须要在页面上通过一番操做以后才会发生,而当问题重现以后,以前那些可用的页面就变得不可用了,而后就会在可用和不可用之间交替......前端

我接到问题的第一反应是java

2、踩坑

2.1 寻找罪魁祸首

代码中request为空,可是前端有传递,第一时间想到的就是线程切换致使ThreadLocal传递出现问题。git

然而这个坑咱们以前是踩过的,而且已经在切面中手动改为了可继承的线程变量github

HttpServletRequest servletRequest = WebUtil.getRequest();
HttpServletResponse servletResponse = WebUtil.getResponse();
//声明子线程的时候,这些属性不会继承,手动赋值成可继承的属性
ServletRequestAttributes attributes = new ServletRequestAttributes(servletRequest, servletResponse);
RequestContextHolder.setRequestAttributes(attributes, true);
LocaleContextHolder.setLocaleContext(LocaleContextHolder.getLocaleContext(), true);

难道切面没生效?缓存

但是通过调试发现,这段代码是进入并执行了的。测试

经过查看提交记录发现,切面中有人加了这么一段代码(没错就是我)this

ExecutorService TIMEOUT_EXECUTOR_POOL = new ThreadPoolExecutor(
    Runtime.getRuntime().availableProcessors() + 1, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS,
    new SynchronousQueue<>(), ThreadUtil.newNamedThreadFactory("TIMEOUT_EXECUTOR_POOL", false)
);
FutureTask<Object> futureTask = new FutureTask<>(() -> {
    try {
        return joinPoint.proceed();
    } catch (Exception ex) {
        throw ex;
    } catch (Throwable throwable) {
        throw new Exception(throwable);
    }
});
TIMEOUT_EXECUTOR_POOL.submit(futureTask);

为了增长超时时间的控制,我用FutureTask把执行的代码包装了一层线程

在这里打断点调试,发如今报错的时候,futureTask外部request参数有值,进入后参数为空。调试

可是,偶尔也是会有值的!有值的时候就是页面正常的时候。code

2.2 找出做案动机(缘由)

咱们先看下InheritableThreadLocal是怎么实现线程变量可继承的

在Thread的init()方法中有一段代码

private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize, AccessControlContext acc,
                  boolean inheritThreadLocals) {
    //省略部分代码
   
    //若是父线程inheritableThreadLocals不为空,则保存下来
    if (inheritThreadLocals && parent.inheritableThreadLocals != null)
        this.inheritableThreadLocals =
        ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
    
    //省略部分代码
}

能够看到InheritableThreadLocal是在Thread建立的时候继承的。

而咱们知道线程池的做用就是“缓存”线程来避免线程频繁的建立和销毁,因此若是在线程池中使用InheritableThreadLocal,只有第一个建立线程时的请求是能够用的,后续请求的InheritableThreadLocal都跟第一个请求同样,不会再改变。

至此,问题缘由找到了,由于我建立线程池的时候初始化了CPU核数+1个线程,因此开始一些请求是正常的,后续当这些线程都使用了以后,就会由于InheritableThreadLocal不一样致使错误。并且咱们本身测试的时候是在几个按钮中重复点击,若是线程的第一个请求是/user/query,当你再次发起这个请求的时候若是恰好分配的是这个线程,页面就是正常的,因而就出现页面时好时坏的状况.

3、填坑

OK,出现问题的地方找到了,下面来解决

一、直接注释掉这段超时控制的代码

这个实在是太粗暴了,只适合紧急状况下使用,做为一个有追求的程序猿,我是不可能这么作的

二、不用线程池,直接new Thread

既然是线程池复用致使的问题,不用线程池就能够解决

三、使用阿里的TransmittableThreadLocal

https://github.com/alibaba/transmittable-thread-local

阿里巴巴开源了一个相似于InheritableThreadLocal的库,就是用来在线程池中使用,有兴趣的能够瞅一眼

相关文章
相关标签/搜索