這兩天一直在查無線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變量也就無法傳遞給線程池中的線程。
- import java.util.concurrent.Executor;
- importjava.util.concurrent.Executors;
- public classThreadLocalTest {
- private staticThreadLocal<String> vLocal = newThreadLocal<String>();
- public static voidmain(String[] args) {
- Executorexecutor = Executors.newFixedThreadPool(2);
- // 模擬10個請求
- for (int i =0; i < 10; i++) {
- final int 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)。