線程池使用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的庫,就是用來在線程池中使用,有興趣的能夠瞅一眼

相關文章
相關標籤/搜索