多線程中ThreadLocal踩坑

前言

記錄一下在測試過程中,遇到的一個(gè)有關(guān)ThreadLocal的問題,順便學(xué)習(xí)一下ThreadLocal相關(guān)的知識。

ThreadLocal介紹

ThreadLocal是一個(gè)關(guān)于創(chuàng)建線程局部變量的類。

要點(diǎn):

  • 在當(dāng)前線程中,任何一個(gè)地方都可以訪問到ThreadLocal的值。
  • 每個(gè)線程里面都有一個(gè)ThreadLocalMap變量,初始值為null,這個(gè)變量的值由ThreadLocal來維護(hù)
  • 當(dāng)前線程保存在ThreadLocal中的值只能被當(dāng)前線程訪問,一般情況下其他線程訪問不到。
  • ThreadLocalMap存儲數(shù)據(jù)方式類似Map的key-value存儲方式,只不過ThreadLocal是以當(dāng)前線程為keyvalue可以為任意類型的值

問題場景

最近項(xiàng)目需要上線一個(gè)大版本,此次版本對前端APP新、老版本發(fā)起的請求做了不同的加密處理,經(jīng)過討論,需要在后臺做版本兼容。

兼容的流程:

  • APP端在請求頭里面新增一個(gè)字段作為新版本APP的標(biāo)識,如:varA:123
  • 后端在SpringDispatcherServlet中判斷varA是否為空,若不為空則把它放入ThreadLocal變量中
    if (StringUtil.isNotEmpty(varA)){
      ThreadContext.put(ThreadContext.FLAG, varA);
    }
    
  • 然后在JsonHttpMessageConverter(自定義請求解析類)中,根據(jù)varA是否為空來決定采取哪種解密方式來解密請求
    String flag = ThreadLocal.get(ThreadContext.FLAG);
    if (StringUtil.isNotEmpty(flag)){
      //新版本解密方式
    }else{
      //老版本解密方式
    }
    
  • 邏輯處理
  • 響應(yīng)請求

問題描述

按照上面的兼容流程做完代碼更改之后,在本地測試沒有問題,但是放在測試環(huán)境,由測試人員測試就有問題。

具體問題描述:

  • 老版本APP發(fā)起的請求在后臺解密時(shí)會進(jìn)入新版本APP解密方式的判斷里面去,但是只是部分請求才會出現(xiàn)此情況

分析結(jié)果

我們知道,后端應(yīng)用服務(wù)器在處理請求時(shí),會對每一個(gè)請求分配一個(gè)線程來處理,如果每次來一個(gè)請求都去新開一個(gè)線程,然后響應(yīng)請求之后又去銷毀線程,這樣的結(jié)果不僅會增加請求響應(yīng)時(shí)間,而且還會大大提高系統(tǒng)資源消耗。

所以為了適應(yīng)高并發(fā)請求,在應(yīng)用服務(wù)器端都會使用線程池來處理請求,效果是減少系統(tǒng)資源開銷以及加快請求響應(yīng)時(shí)間。

前面講到,由于ThreadLocal是以當(dāng)前線程為key,所以如果前后有兩條請求發(fā)到后臺,并且這兩條請求都是使用的線程池里面的同一個(gè)線程。并且第一條是新版本APP發(fā)過來的帶有標(biāo)識的請求,第二條是老版本APP發(fā)過來的不帶標(biāo)識的請求。

第一條請求把標(biāo)識存入ThreadLocal變量中,在響應(yīng)完請求之后沒有及時(shí)的清理掉ThreadLocal中的值

當(dāng)?shù)诙l不帶標(biāo)識的請求到來時(shí),由于在SpringDispatcherServlet中做了不為空才把標(biāo)識放入ThreadLocal中,所以這里就沒有更新ThreadLocal中的值,但其實(shí)由于前面一個(gè)請求響應(yīng)之后沒有清理掉ThreadLocal中的值,所以在JsonHttpMessageConverter中獲取當(dāng)前線程的標(biāo)識時(shí),還是有值,這樣就會進(jìn)入新版本的解密方式中去。

問題處理

兩種方式:

  • SpringDispatcherServlet中不做判空處理,從請求中不管獲取到什么值都存入ThreadLocal變量中,以此達(dá)到實(shí)時(shí)更新值的效果

  • 在響應(yīng)完請求之后移除ThreadLocal中想要移除的值或清空ThreadLocal里面當(dāng)前線程保存的所有值

    ThreadContext.remove(ThreadContext.FLAG);
    或者清空所有
    ThreadContext.remove();
    

最后我采取的第二種方式,因?yàn)榘催壿嬍?strong>ThreadLocal里面的數(shù)據(jù)只適合在本次請求中使用,使用完了之后就得清理掉

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,502評論 19 139
  • 線程池ThreadPoolExecutor corepoolsize:核心池的大小,默認(rèn)情況下,在創(chuàng)建了線程池之后...
    irckwk1閱讀 863評論 0 0
  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時(shí)...
    歐辰_OSR閱讀 30,187評論 8 265
  • 進(jìn)程和線程 進(jìn)程 所有運(yùn)行中的任務(wù)通常對應(yīng)一個(gè)進(jìn)程,當(dāng)一個(gè)程序進(jìn)入內(nèi)存運(yùn)行時(shí),即變成一個(gè)進(jìn)程.進(jìn)程是處于運(yùn)行過程中...
    小徐andorid閱讀 2,966評論 3 53
  • 夢想是一輩子路,目標(biāo)是一陣子路 從小到大,老師和家長都常說:一個(gè)人應(yīng)該擁有夢想。那個(gè)時(shí)候我們誤以為想吃一個(gè)蛋糕或...
    丶煙花雨閱讀 310評論 0 0

友情鏈接更多精彩內(nèi)容