某天测试环境更新后,有小伙伴反应页面会随机性的发生请求参数为空的状况(request.getParamter为空),可是前端的参数是传了的,并且不能稳定重现,须要在页面上通过一番操做以后才会发生,而当问题重现以后,以前那些可用的页面就变得不可用了,而后就会在可用和不可用之间交替......前端
我接到问题的第一反应是java
代码中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
咱们先看下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,当你再次发起这个请求的时候若是恰好分配的是这个线程,页面就是正常的,因而就出现页面时好时坏的状况.
OK,出现问题的地方找到了,下面来解决
一、直接注释掉这段超时控制的代码
这个实在是太粗暴了,只适合紧急状况下使用,做为一个有追求的程序猿,我是不可能这么作的
二、不用线程池,直接new Thread
既然是线程池复用致使的问题,不用线程池就能够解决
三、使用阿里的TransmittableThreadLocal
https://github.com/alibaba/transmittable-thread-local
阿里巴巴开源了一个相似于InheritableThreadLocal的库,就是用来在线程池中使用,有兴趣的能够瞅一眼