`
iamlotus
  • 浏览: 106613 次
  • 性别: Icon_minigender_1
  • 来自: 上海
文章分类
社区版块
存档分类
最新评论

Tomcat的Request不是线程安全的

 
阅读更多

这几天遇到一个和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包。每次遇到多线程的问题,由于不能固定重现,大多数时间都是在脑海中画时序图,猜哪条路径会出错,太坑爹了!

 

0
1
分享到:
评论
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  
分析的很精彩。

相关推荐

    基于tomcat的连接数与线程池详解

    在使用tomcat时,经常会遇到连接数、线程数之类的配置问题,要真正理解这些概念,必须先了解Tomcat的连接器(Connector)。 在前面的文章 详解Tomcat配置文件server.xml 中写到过:Connector的主要功能,是接收连接...

    运行在tomcat容器中的ThreadLocal容易产生的问题

    运行在tomcat容器中的ThreadLocal容易产生的问题ThreadLocal在tomcat容器中的的生命周期并不等于web request的生命周期,所以(以下讨论的是tomcat容器中使用ThreadLocal),所以ThreadLocal不应保存与请求会影响的...

    jsp全攻略,tomcat技术

    enableLookups 如果为true,则可以通过调用request.getRemoteHost()进行DNS查询来得到远程客户端的实际主机名,若为false则不进行DNS查询,而是返回其ip地址 redirectPort 指定服务器正在处理http请求时收到了一个...

    tomcat服务器的Server.xml配置详解.docx

    Connector 的主要功能,是接收连接请求,创建Request和Response对象...然后分配线程让Engine来处理这个请求,并把产生的Request和Response对象传给Engine。通过配置Connector,可以控制请求Service的协议及端口号。

    how-tomcat-works

    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 ...

    How Tomcat Works: A Guide to Developing Your Own Java Servlet Container

    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 ...

    超级有影响力霸气的Java面试题大全文档

    HashMap是Hashtable的轻量级实现(非线程安全的实现),他们都完成了Map接口,主要区别在于HashMap允许空(null)键值(key),由于非线程安全,效率上可能高于Hashtable。 HashMap允许将null作为一个entry的key或者...

    从J2SE到J2EE知识点介绍

    (一) myeclipse上配置Tomcat服务器 101 (二) Jsp+servlet+bean版HelloWorld概览 105 (三) Jsp基本页面标签 116 1. 标签 116 2. contentType属性 118 3. pageEncoding属性 118 (四) form表单中的action,post和get ...

    java 面试题 总结

    HashMap是Hashtable的轻量级实现(非线程安全的实现),他们都完成了Map接口,主要区别在于HashMap允许空(null)键值(key),由于非线程安全,效率上可能高于Hashtable。 HashMap允许将null作为一个entry的key或者...

    C 网络服务开发套件0.5.0

    集成的C 通用通讯平台, 类似于JAVA的WEB 服务器(如TOMCAT、JBOSS等); 统一管理C 的SOCKET通讯、线程沲管理、调用管理等; 可以注册为NT服务, 具实时监控重启功能; b. 通讯 基于XML文本通信协议,客户端可以方便...

    C 网络服务开发套件

    集成的C 通用通讯平台, 类似于JAVA的WEB 服务器(如TOMCAT、JBOSS等); 统一管理C 的SOCKET通讯、线程沲管理、调用管理等; 可以注册为NT服务, 具实时监控重启功能; b. 通讯 基于XML文本通信协议,客户端可以方便...

    C++网络开发框架0.5.7

    集成的C++通用通讯平台, 类似于JAVA的WEB 服务器(如TOMCAT、JBOSS等); 统一管理C++的SOCKET通讯、线程沲管理、调用管理等; 可以注册为NT服务, 具实时监控重启功能; b. 通讯 基于XML文本通信协议,客户端可以方便...

    java初学者必看

    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管理系统 ...

    java面试题

    45. Request对象的主要方法: 21 46. jsp有哪些动作?作用分别是什么? 21 47. 两种跳转方式分别是什么?有什么区别? 22 48. get和post的区别? 22 49. JDK,JRE,JVM的区别? 22 50. Java中常见类,方法,接口 23 51. 多...

    C++网络服务开发套件0.5.3

    统一管理C++的SOCKET通讯、线程沲管理、调用管理等; 可以注册为NT服务, 具实时监控重启功能; &lt;br&gt;b. 通讯 基于XML文本通信协议,客户端可以方便应用于各种环境平台和硬件平台,如Windows、Linux,PDA等...

    web服务器技术

    通过对底层的服务的书写,通过request,response请求与响应,来完成数据的交互,并且利用多线程,实现多次访问。

    C++网络服务开发套件0.5.5

    统一管理C++的SOCKET通讯、线程沲管理、调用管理等; 可以注册为NT服务, 具实时监控重启功能; &lt;br&gt;b. 通讯 基于XML文本通信协议,客户端可以方便应用于各种环境平台和硬件平台,如Windows、Linux,PDA等...

    C++网络服务开发套件0.5.6

    统一管理C++的SOCKET通讯、线程沲管理、调用管理等; 可以注册为NT服务, 具实时监控重启功能; &lt;br&gt;b. 通讯 基于XML文本通信协议,客户端可以方便应用于各种环境平台和硬件平台,如Windows、Linux,PDA等...

    java基础题 很全面

    53. 一个".java"源文件中是否可以包括多个类(不是内部类)?有什么限制? 13 54. java中有几种方法可以实现一个线程?用什么关键字修饰同步方法? stop()和suspend()方法为何不推荐使用? 13 55. java中有几种类型的流?...

Global site tag (gtag.js) - Google Analytics