某天測試環境更新後,有小夥伴反應頁面會隨機性的發生請求參數爲空的狀況(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的庫,就是用來在線程池中使用,有興趣的能夠瞅一眼