threadLocal遇上執行緒池導致區域性變數變化
這兩天一直在查無線app一個詭異的問題,表象是stg的介面返回資料,和線上介面的返回資料不一致。
1、初步判斷:有快取,檢視程式碼後發現快取時間直郵6分鐘,而且同一個介面,其他呼叫方的返回資料,stg和線上是保持一致的。
2、確認版本後,把線上版本和stg環境的版本號,進行多次check,發現版本是一致的。
3、線上和stg介面的返回資料,來源於我依賴的介面,現在介面stg和線上是不一致,而不是一個有資料一個沒資料,判斷是呼叫了不同的介面。瞭解下來介面會根據不同的版本號返回不同的資料,所以判斷有版本控制的appClientVersion這個欄位傳的不對,安裝最新的app包,debug我們的stg環境發現版本是4.0.3沒有傳錯。在各種解釋不通的情況下,我只好加上日誌,把輸入輸出打出來。
上線後檢視日誌發現:我的屌絲Android手機居然變成了iphone,版本號也是4.0.1,起初懷疑無線版本號不對,連上Fiddler,並切換線上和stg環境,發現請求的clientInfo沒有錯,的確是android ,4.0.3的版本,那問題肯定是venus到我們的服務再到我們呼叫服務之前clientInfo被改動了。檢視程式碼發現,clientInfo資訊是從ThreadLocal裡面拿的。。。原來拿的是別的執行緒的內容,怪不得連屌絲機都能升級成高富帥。這就可以解釋為什麼stg永遠好的,線上有問題,因為stg測試的全是4.0.3版本的釋出包測的。
我們的版本控制是控制interfaceVersion來控制的,再拿到ThreadLocal裡面的內容的時候,我們重新賦值了,所以,這個引數沒有問題,而appClientVersion和ClientSystem都沒有重新賦值,拿到別的執行緒的內容後就變成了我們所依賴的介面的老版本,所以返回了不同的資料。
ThreadLocal可以為當前執行緒儲存區域性變數,而InheritableThreadLocal則可以在建立子執行緒的時候將父執行緒的區域性變數傳遞到子執行緒中。
如果使用了執行緒池(如Executor),那麼即使即使父執行緒已經結束,子執行緒依然存在並被池化。這樣,執行緒池中的執行緒在下一次請求被執行的時候,ThreadLocal物件的get()方法返回的將不是當前執行緒中設定的變數,因為池中的“子執行緒”根本不是當前執行緒建立的,當前執行緒設定的ThreadLocal變數也就無法傳遞給執行緒池中的執行緒。
[java] view plain copy print?- import java.util.concurrent.Executor;
- importjava.util.concurrent.Executors;
- public
- private staticThreadLocal<String> vLocal = newThreadLocal<String>();
- publicstatic voidmain(String[] args) {
- Executorexecutor = Executors.newFixedThreadPool(2);
- // 模擬10個請求
- for (int i =0; i < 10; i++) {
- finalint flag= i;
- executor.execute(new Runnable() {
- @Override
- public voidrun() {
- // vLocal.set(null);
- //模擬某一執行緒改變了ThreadLocal的值
- if (flag == 1) {
- vLocal.set("set:test");
- }
- System.out.println(Thread.currentThread().getName()+ ":" + vLocal.get());
- }
- });
- }
- }
- }
pool-1-thread-1:null
pool-1-thread-2:set:test
pool-1-thread-1:null
pool-1-thread-2:set:test
pool-1-thread-1:null
pool-1-thread-2:set:test
pool-1-thread-1:null
pool-1-thread-2:set:test
pool-1-thread-1:null
pool-1-thread-2:set:test
因此,必須將外部執行緒中的ThreadLocal變數顯式地傳遞給執行緒池中的執行緒,或者每個請求來的時候先threadLocal.set(null)。