當前位置:
首頁 > 最新 > ThreadLocal引起的一次詭異問題

ThreadLocal引起的一次詭異問題

現在有一個需求:在一個系統A中,要調另一個系統B中的兩張表,數據量大概100多萬,初始化到A系統的一個統計表中。我使用的是多線程,每個線程從A系統調用B系統,取出幾百條數據進行處理,防止取數據太多,造成內存溢出。需要說明的是,我們訪問介面的時候在請求頭中需要帶著一個accesstoken欄位,這是登錄的時候分配的,然後在攔截器中判斷這個欄位是否存在,是否可以從redis中取出對應的登錄用戶信息完成鑒權,如果鑒權通過,會把這個accesstoken放在ThreadLocal中(下面出現的問題就是跟這有關),每次請求都會設置一次。


1.使用多線程查詢並處理數據

1.1控制層接收請求

使用ThreadPoolExecutor創建一個線程池pool,而後查詢總記錄數,每個線程每次查limit條記錄,每次起thread_num個線程,計算經過多少輪可以將所有記錄處理完畢,實際執行的線程ExecuteTask創建過程如下:

1.2創建多線程處理請求

每次根據起始位置,和查詢條數,調tasksProxy服務查詢固定條數記錄,而後遍歷,再次調用usersProxy和tasksProxy獲取指定id的記錄信息,組裝成新的對象,放在list集合中,批量的插入資料庫。

1.3可以採用注入的方式創建線程池

在上文中使用的ThreadPoolExecutor創建線程池,可以使用注入的方式。

在需要使用的類里直接注入就可以了,如下:

這時候statisticsStudentErExecutor就跟上面的pool效果一樣了。

1.4 在tasksProxy實現介面調用的服務

tasksProxy就是上面說的B服務,需要在這裡面寫介面,供A服務進行調用,我這裡只舉一個示例:

分頁查詢指定條數的記錄,這裡會調用真正實現類的controller,在請求頭中帶著access-token發請求過去,其它幾個介面實現也類似。

我們是發請求到A服務,A服務處理業務時再調用B服務。其中進入A服務時攔截器會設置accesstoken到ThreadLocal中,然後使用的時候可以通過GlobalRequestContext.getAccessToken()獲得,這在單一的線程中並沒有問題,一個請求一個線程處理,ThreadLocal不變,多線程就會出問題,ThreadLocal是屬於主線程的,在子線程中再通過GlobalRequestContext.getAccessToken()是獲取不到的。一開始B服務的getERStudentTaskListByPageWithAuth(Integer start, Integer limit)介面是這樣的,並沒有傳accessToken過來,直接在headers.add("access-token",GlobalRequestContext.getAccessToken())中這樣設置,然後發請求,那麼沒有許可權,多線程中調用B系統沒有一點反應,像死了一樣。更糟糕的是我還專門測了getERStudentTaskListByPageWithAuth方法沒有問題,這讓我堅信不是這裡的問題,其實單側這一個請求時,一個主線程處理,而且傳了accesstoken過來,所以當然是沒有問題的。我在本地電腦測試的時候為了方便,headers裡面的access_token是寫死的,所以也沒有問題,放在線上都不行了,所以看似詭異,還是自己太粗心,最終在同事的幫助下找到了這個問題。總結:1.ThreadLocal裡面存放的數據是屬於某一個線程的,如果在這個主線程中又開了多線程,那麼在多線程中是取不到主線程ThreadLocal的值的,可以在構造函數中傳入主線程ThrealLocal的值到多線程中。2.Runnable創建的多線程沒有返回結果,找不到問題調試時可以採用Callable,獲取線程執行之後的結果,可以使用jstack查看線程的運行狀態,判斷是否發生了死鎖等問題。


上面的是每輪開啟5個線程,每個查詢300條,然後處理數據;也可以主線程只負責查詢,一次查詢300條記錄,然後交給多線程去處理數據,後面這種可能沒有第一種效率高,但是第一種有一個要求,就是要知道每輪查詢時每個線程的起始查詢記錄。代碼如下:

主線程將每次查詢出的集合list和access_token傳給多線程進行處理。


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 Java開發日記 的精彩文章:

TAG:Java開發日記 |