servlet及jsp中多线程同步

servlet/jsp技术和asp、php等相比,由于其多线程运行而具有很高的执行效率。由于servlet/jsp默认是以多线程模式执行的,所以,在编写代码时需要非常细致地考虑多线程的同步问题。然而,很多人编写servlet/jsp程序时并没有注意到多线程同步的问题,这往往造成编写的程序在少量用户访问时没有任何问题,而在并发用户上升到一定值时,就会经常出现一些莫明其妙的问题,对于这类随机性的问题调试难度也很大。

  一、在servlet/jsp中的几种变量类型

  在编写servlet/jsp程序时,对实例变量一定要小心使用。因为实例变量是非线程安全的。在servlet/jsp中,变量可以归为下面的几类:

  1. 类变量

  request,response,session,config,application,以及jsp页面内置的page, pagecontext。其中除了application外,其它都是线程安全的。

  2. 实例变量

  实例变量是实例所有的,在堆中分配。在servlet/jsp容器中,一般仅实例化一个servlet/jsp实例,启动多个该实例的线程来处理请求。而实例变量是该实例所有的线程所共享,所以,实例变量不是线程安全的。

  3. 局部变量

  局部变量在堆栈中分配,因为每一个线程有自己的执行堆栈,所以,局部变量是线程安全的。

  二、在servlet/jsp中的多线程同步问题

 


首先定义了两个实例变量,username和password。然后在从request中获取这两个参数,并调用showuserinfo()方法将请求用户的信息回显在该客户的浏览器上。在一个用户访问是,不存在问题。但在多个用户并发访问时,就会出现其它用户的信息显示在另外一些用户的浏览器上的问题。这是一个严重的问题。为了突出并发问题,便于测试、观察,我们在回显用户信息时执行了一个模拟的费时操作,比如,下面的两个用户同时访问(可以启动两个ie浏览器,或者在两台机器上同时访问):

a: http://localhost:8080/instanceconcurrenttest.jsp?username=a&password=123

b: http://localhost:8080/instanceconcurrenttest.jsp?username=b&password=456

如果a点击链接后,b再点击链接,那么,a将返回一个空白屏幕,b则得到a以及b两个线程的输出。


从运行结果的截图上可以看到,web服务器启动了两个线程分别来处理来自a和b的请求,但是在a却得到一个空白的屏幕。这是因为上面程序中的output, username和password都是实例变量,是所有线程共享的。在a访问该页面后,将output设置为a的输出,username,password分别置为a的信息,而在a执行printuserinfo()输出username和password信息前,b又访问了该页面,把username和password置为了b的信息,并把输出output指向到了b。随后a的线程打印时,就打印到了b的屏幕了,并且,a的用户名和密码也被b的取代。

而实际程序中,由于设置实例变量,使用实例变量这两个时间点非常接近,所以,像本例的同步问题并没有这么突出,可能会偶尔出现,但这却更加具有危险性,也更加难于调试。

  同样,对于servlet也存在实例变量的多线程问题,

// instanceconcurrenttest.java
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.printwriter;
public class instanceconcurrenttest extends httpservlet
 { 
      string username; 
      string password; 
      printwriter out; 
      public void doget(httpservletrequest request,
       httpservletresponse response)         
       throws servletexception,java.io.ioexception 
       {  
           //从request中获取参数  
    username = request.getparameter("username");
    password = request.getparameter("password"); 
    system.out.println(thread.currentthread().getname() +
     " | set username:" + username);
    out = response.getwriter(); 
    showuserinfo();  
       } 
       public void showuserinfo() {  
           //为了突出并发问题,在这儿首先执行一个费时操作  
    int i =0;  
    double sum = 0.0;  
    while (i++ <  200000000) { 
      sum += i;  
    }  
    out.println("thread:" + thread.currentthread().getname());
    out.println("username:"+ username); 
    out.println("password:" + password); 
 }
}
 


  三、解决方案

  1. 以单线程运行servlet/jsp

  在jsp中,通过设置:,在servlet中,通过实现javax.servlet.singlethreadmodel,此时web容器将保证jsp或servlet实例以单线程方式运行。

  重要提示:在测试中发现,tomcat 4.1.17不能正确支持isthreadsafe属性,所以,指定istheadsafe为false后,在tomcat 4.1.17中仍然出现多线程问题,这是tomcat 4.1.17的bug。在tomcat 3.3.1和resin 2.1.5中测试通过。

  2. 去除实例变量,通过参数传递

  从上面的分析可见,应该在servlet/jsp中尽量避免使用实例变量。比如,下面的修正代码,去除了实例变量,通过定义局部变量,并参数进行传递。这样,由于局部变量是在线程的堆栈中进行分配的,所以是线程安全的。不会出现多线程同步的问题。代码如下:

 


 


注:有的资料上指出在printuserinfo()方法或者实例变量的相关操作语句上使用synchronized关键字进行同步,但这样并不能解决多线程的问题。因为,这样虽然可以使对实例变量的操作代码进行同步,但并不能阻止一个线程使用另外一个线程修改后的“脏的”实例变量。所以,除了降低运行效率外,不会起到预期效果。

      (责任编辑:包春林)


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值