这几天遇到一个和Request及Cookie相关的问题,再次验证了多线程是魔鬼的道理。
这是从一个第三方jar中反编译出来的代码,程序间歇性的抛NullPointerException,看stack trace是第8行抛的。
public String getCookieVal(String key)
{
Cookie[] cookies = this.req.getCookies();
if (cookies == null) {
return null;
}
for (int i = 0; i if (key.equals(cookie.getName())) {
return cookie.getValue();
}
}
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中的实现
/*
* Return the set of Cookies received with this Request.
*/
public Cookie[] getCookies() {
if (!cookiesParsed)
parseCookies();
return cookies;
}
parseCookies源码如下
/**
* Parse cookies.
*/
protected void parseCookies() {
cookiesParsed = true;
Cookies serverCookies = coyoteRequest.getCookies();
int count = serverCookies.getCookieCount();
if (count <= 0)
return;
cookies = new Cookie[count];
int idx=0;
for (int i = 0; i < count; i++) {
ServerCookie scookie = serverCookies.getCookie(i);
try {
/*
we must unescape the '\\' escape character
*/
Cookie cookie = new Cookie(scookie.getName().toString(),null);
int version = scookie.getVersion();
cookie.setVersion(version);
cookie.setValue(unescape(scookie.getValue().toString()));
cookie.setPath(unescape(scookie.getPath().toString()));
String domain = scookie.getDomain().toString();
if (domain!=null) cookie.setDomain(unescape(domain));//avoid NPE
String comment = scookie.getComment().toString();
cookie.setComment(version==1?unescape(comment):null);
cookies[idx++] = cookie;
} catch(IllegalArgumentException e) {
// Ignore bad cookie
}
}
if( idx < count ) {
Cookie [] ncookies = new Cookie[idx];
System.arraycopy(cookies, 0, ncookies, 0, idx);
cookies = ncookies;
}
}
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包。每次遇到多线程的问题,由于不能固定重现,大多数时间都是在脑海中画时序图,猜哪条路径会出错,太坑爹了!
分享到:
相关推荐
在使用tomcat时,经常会遇到连接数、线程数之类的配置问题,要真正理解这些概念,必须先了解Tomcat的连接器(Connector)。 在前面的文章 详解Tomcat配置文件server.xml 中写到过:Connector的主要功能,是接收连接...
运行在tomcat容器中的ThreadLocal容易产生的问题ThreadLocal在tomcat容器中的的生命周期并不等于web request的生命周期,所以(以下讨论的是tomcat容器中使用ThreadLocal),所以ThreadLocal不应保存与请求会影响的...
enableLookups 如果为true,则可以通过调用request.getRemoteHost()进行DNS查询来得到远程客户端的实际主机名,若为false则不进行DNS查询,而是返回其ip地址 redirectPort 指定服务器正在处理http请求时收到了一个...
Connector 的主要功能,是接收连接请求,创建Request和Response对象...然后分配线程让Engine来处理这个请求,并把产生的Request和Response对象传给Engine。通过配置Connector,可以控制请求Service的协议及端口号。
8.5.5 开启新线程执行类的重新载入 56 8.6 WebappClassLoader类 57 8.6.1 类缓存 58 8.6.2 载入类 59 8.6.3 应用程序 59 第9章 session管理 62 9.1 概述 62 9.2 Sessions 62 9.2.1 Session接口 62 9.2.2 ...
8.5.5 开启新线程执行类的重新载入 56 8.6 WebappClassLoader类 57 8.6.1 类缓存 58 8.6.2 载入类 59 8.6.3 应用程序 59 第9章 session管理 62 9.1 概述 62 9.2 Sessions 62 9.2.1 Session接口 62 9.2.2 ...
HashMap是Hashtable的轻量级实现(非线程安全的实现),他们都完成了Map接口,主要区别在于HashMap允许空(null)键值(key),由于非线程安全,效率上可能高于Hashtable。 HashMap允许将null作为一个entry的key或者...
(一) myeclipse上配置Tomcat服务器 101 (二) Jsp+servlet+bean版HelloWorld概览 105 (三) Jsp基本页面标签 116 1. 标签 116 2. contentType属性 118 3. pageEncoding属性 118 (四) form表单中的action,post和get ...
HashMap是Hashtable的轻量级实现(非线程安全的实现),他们都完成了Map接口,主要区别在于HashMap允许空(null)键值(key),由于非线程安全,效率上可能高于Hashtable。 HashMap允许将null作为一个entry的key或者...
集成的C 通用通讯平台, 类似于JAVA的WEB 服务器(如TOMCAT、JBOSS等); 统一管理C 的SOCKET通讯、线程沲管理、调用管理等; 可以注册为NT服务, 具实时监控重启功能; b. 通讯 基于XML文本通信协议,客户端可以方便...
集成的C 通用通讯平台, 类似于JAVA的WEB 服务器(如TOMCAT、JBOSS等); 统一管理C 的SOCKET通讯、线程沲管理、调用管理等; 可以注册为NT服务, 具实时监控重启功能; b. 通讯 基于XML文本通信协议,客户端可以方便...
集成的C++通用通讯平台, 类似于JAVA的WEB 服务器(如TOMCAT、JBOSS等); 统一管理C++的SOCKET通讯、线程沲管理、调用管理等; 可以注册为NT服务, 具实时监控重启功能; b. 通讯 基于XML文本通信协议,客户端可以方便...
17.6.2 request对象 17.6.3 response对象 17.6.4 session对象 17.6.5 application对象 17.6.6 pageContext对象 17.6.7 exception对象 17.6.8 config对象 17.7 计数器 17.8 本章习题 第18章 KTV管理系统 ...
45. Request对象的主要方法: 21 46. jsp有哪些动作?作用分别是什么? 21 47. 两种跳转方式分别是什么?有什么区别? 22 48. get和post的区别? 22 49. JDK,JRE,JVM的区别? 22 50. Java中常见类,方法,接口 23 51. 多...
统一管理C++的SOCKET通讯、线程沲管理、调用管理等; 可以注册为NT服务, 具实时监控重启功能; <br>b. 通讯 基于XML文本通信协议,客户端可以方便应用于各种环境平台和硬件平台,如Windows、Linux,PDA等...
通过对底层的服务的书写,通过request,response请求与响应,来完成数据的交互,并且利用多线程,实现多次访问。
统一管理C++的SOCKET通讯、线程沲管理、调用管理等; 可以注册为NT服务, 具实时监控重启功能; <br>b. 通讯 基于XML文本通信协议,客户端可以方便应用于各种环境平台和硬件平台,如Windows、Linux,PDA等...
统一管理C++的SOCKET通讯、线程沲管理、调用管理等; 可以注册为NT服务, 具实时监控重启功能; <br>b. 通讯 基于XML文本通信协议,客户端可以方便应用于各种环境平台和硬件平台,如Windows、Linux,PDA等...
53. 一个".java"源文件中是否可以包括多个类(不是内部类)?有什么限制? 13 54. java中有几种方法可以实现一个线程?用什么关键字修饰同步方法? stop()和suspend()方法为何不推荐使用? 13 55. java中有几种类型的流?...