这两天一直在查无线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
 
  1. import java.util.concurrent.Executor;  
  2. importjava.util.concurrent.Executors;  
  3.    
  4. public classThreadLocalTest {  
  5.    
  6.     private staticThreadLocal<String> vLocal = newThreadLocal<String>();  
  7.    
  8.     public static voidmain(String[] args) {  
  9.    
  10.         Executorexecutor = Executors.newFixedThreadPool(2);  
  11.    
  12.         // 模拟10个请求  
  13.         for (int i =0; i < 10; i++) {  
  14.            final int flag= i;  
  15.            executor.execute(new Runnable() {  
  16.    
  17.                 @Override  
  18.                public voidrun() {  
  19. //                   vLocal.set(null);  
  20.                  //模拟某一线程改变了ThreadLocal的值  
  21.                    if (flag == 1) {  
  22.                        vLocal.set("set:test");  
  23.                    }  
  24.                    System.out.println(Thread.currentThread().getName()+ ":" + vLocal.get());  
  25.                }  
  26.            });  
  27.        }  
  28.    
  29.     }  
  30. }  



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)。