Tomcat的Request不是线程安全的

探讨了Tomcat中Request对象的线程安全性问题,分析了getCookies()方法在多线程环境下可能导致NullPointerException的原因,及如何避免此类问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Tomcat的Request不是线程安全的

 

这几天遇到一个和Request及Cookie相关的问题,再次验证了多线程是魔鬼的道理。


这是从一个第三方jar中反编译出来的代码,程序间歇性的抛NullPointerException,看stack trace是第8行抛的。

 

Java代码 

 收藏代码

  1. public String getCookieVal(String key)  
  2.  {  
  3.    Cookie[] cookies = this.req.getCookies();  
  4.    if (cookies == null) {  
  5.      return null;  
  6.    }  
  7.    for (int i = 0; i   if (key.equals(cookie.getName())) {  
  8.        return cookie.getValue();  
  9.      }  
  10.    }  
  11.    return null;  

 

这行为什么会抛NullPointerException呢?看代码无非key是null或者cookie是null,于是想到加Log,捕捉到NPE时打出传入的key和所有的cookie,看看有没有null。

可是... 从Log看,捕捉到NPE时,key是有值的,所有的cookie也都是有值的,难道见鬼了?


这个bug纠结了两天,才找到了问题所在:正如标题,Request.getCookies()不是线程安全的

先来看一下Servelt 2.4 spec 中关于Request.getCookies()的描述
 

 写道

getCookies()
public Cookie[] getCookies()
Returns an array containing all of the Cookie objects the client sent with this
request. This method returns null if no cookies were sent.

Returns: an array of all the Cookies included with this request, or null if
the request has no cookies

 



  从这个描述来,getCookies方法返回的数组中应该不会包含null才对,但是,按照Tomcat 5.5的源码来看,并不是这样,

以下是org.apache.catalina.connector.Request中的实现

Java代码 

 收藏代码

  1. /* 
  2.    * Return the set of Cookies received with this Request. 
  3.    */  
  4.   public Cookie[] getCookies() {  
  5.   
  6.       if (!cookiesParsed)  
  7.           parseCookies();  
  8.   
  9.       return cookies;  
  10.   
  11.   }  

 parseCookies源码如下

Java代码 

 收藏代码

  1.    
  2. /** 
  3.      * Parse cookies. 
  4.      */  
  5.     protected void parseCookies() {  
  6.   
  7.         cookiesParsed = true;  
  8.   
  9.         Cookies serverCookies = coyoteRequest.getCookies();  
  10.         int count = serverCookies.getCookieCount();  
  11.         if (count <= 0)  
  12.             return;  
  13.   
  14.         cookies = new Cookie[count];  
  15.   
  16.         int idx=0;  
  17.         for (int i = 0; i < count; i++) {  
  18.             ServerCookie scookie = serverCookies.getCookie(i);  
  19.             try {  
  20.                 /* 
  21.                 we must unescape the '\\' escape character 
  22.                 */  
  23.                 Cookie cookie = new Cookie(scookie.getName().toString(),null);  
  24.                 int version = scookie.getVersion();  
  25.                 cookie.setVersion(version);  
  26.                 cookie.setValue(unescape(scookie.getValue().toString()));  
  27.                 cookie.setPath(unescape(scookie.getPath().toString()));  
  28.                 String domain = scookie.getDomain().toString();  
  29.                 if (domain!=null) cookie.setDomain(unescape(domain));//avoid NPE  
  30.                 String comment = scookie.getComment().toString();  
  31.                 cookie.setComment(version==1?unescape(comment):null);  
  32.                 cookies[idx++] = cookie;  
  33.             } catch(IllegalArgumentException e) {  
  34.                 // Ignore bad cookie  
  35.             }  
  36.         }  
  37.         if( idx < count ) {  
  38.             Cookie [] ncookies = new Cookie[idx];  
  39.             System.arraycopy(cookies, 0, ncookies, 0, idx);  
  40.             cookies = ncookies;  
  41.         }  
  42.   
  43.     }  

  

getCookies() 的流程是先置cookiesParsed,再生成并初始化cookies[]数组,并且没有同步,所以,若两个线程在同一个Request上按照以下时间步骤调用getCookies方法,就会出错,


Thread A                                         Thread B



1 getCookies();


2 parseCookie();


3 cookiesParsed = true;


4 cookies = new Cookie[count];



5                                                       getCookies();


6                                                      cookiesParsed==true;


7                                                       return cookies;




如果cookies 数组刚被new 出来,还没有完成初始化,ThreadA就让出了时间片,ThreadB接着调用getCookies()就可能读到一个没有初始化完成的cookies,这个cookies中包含null元素。



这同时也解释了为什么ThreadB的log中request.getCookies()都是有值的:当ThreadB的log再次调用request.getCookies()时,ThreadA早已再次获得时间片,并完成了cookies的初始化,所以log中getCookies()拿到的都是好的发现不了问题。



定位了问题后,解决方法是从源头上避免对于request的并发访问,相对就比较容易实现了。



这次debug的总结


1) Request并不是一个线程安全的对象

2) 多线程真心坑爹,要不java也不会推出个conccurent包。每次遇到多线程的问题,由于不能固定重现,大多数时间都是在脑海中画时序图,猜哪条路径会出错,太坑爹了!

 

30W年薪的人工智能工程师只是“白菜价”?

人工智能技术向前发展,也必然会出现一些岗位被人工智能取代,但我们相信,随着人工智能的发展,会有更多的新的、属于未来的工作岗位出现,是社会发展的必然产物,我们能做的也许只能是与时俱进了

0 

1 

分享到:  

Hibernate实现贯穿三层的乐观锁 | hibernate中"fetch all properties" 不起 ...

评论

6 楼 iamlotus 2014-05-08  

wangheng1700 写道

还有线程内部的堆栈都是单独的,在调各自的Request.getCookie方法的时候会各自创建运行时的线程堆栈,怎么会出现B线程去取A线程未初始化的变量呢?


这个是第三方包,会从Request中取cookie信息单独开个线程进行其它的操作。

5 楼 wangheng1700 2013-06-27  

还有线程内部的堆栈都是单独的,在调各自的Request.getCookie方法的时候会各自创建运行时的线程堆栈,怎么会出现B线程去取A线程未初始化的变量呢?

4 楼 wangheng1700 2013-06-27  

同意3楼的看法,怎么可能多个线程里面访问同一个Request!!!!楼主给个说法!

3 楼 alan0509 2012-01-11  

这 代码 我怎么看着 有问题 。求解?


为什么 会出现 若两个线程在同一个Request ?

2 楼 ruby_windy 2011-10-13  

很好的分析.

1 楼 zhufeng1981 2011-10-12  

分析的很精彩。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值