首先,对于servlet来说,它的生命周期是:当服务器接收用户的一个请求,就会初始化一个servlet实例对象,之后调用init()方法来进行初始化。这个实例一旦创建,就会驻留在内存里面,以供后续的用户访问的需要。之后会调用service()方法,来进行分发调用,选择是调用doGet()还是doPost()方法。针对不同的用户,每人会有自己的一份request和response。最后,调用destroy()方法,来销毁实例对象。
那么servlet是如何同时处理多个请求的呢?
Servlet采用多线程来处理多个请求同时访问,Servelet容器维护了一个线程池来服务请求。线程池实际上是等待执行代码的一组线程叫做工作者线程,Servlet容器使用一个调度线程来管理工作者线程。当容器收到一个访问Servlet的请求,调度者线程从线程池中选出一个工作者线程,将请求传递给该线程,然后由该线程来执行Servlet的service方法。当这个线程正在执行的时候,容器收到另外一个请求,调度者线程将从池中选出另外一个工作者线程来服务新的请求,容器并不关心这个请求是否访问的是同一个Servlet还是另外一个Servlet。当容器同时收到对同一Servlet的多个请求,那这个Servlet的service方法将在多线程中并发的执行。servlet默认采用的是单实例多线程的方式来处理用户的请求的。这样减少产生servlet实例的开销,提升了服务器对请求的相应的时间。
线程安全的概念:当多个线程同一个时间访问同一个java实例的时候,不会出现异常。与我们在单线程下运行的结果是一样的。
那么对于servlet来说,线程的安全主要取决于我们多个线程共享的这个servlet实例。
import javax.servlet. *;
import javax.servlet.http. *;
import java.io. *;
public class Concurrent Test extends HttpServlet {
PrintWriter output;
public void service (HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
String username;
Response.setContentType ("text/html; charset=gb2312");
Username = request.getParameter ("username");
output = response.getWriter ();
try{
Thread. sleep (5000); //为了突出并发问题,在这设置一个延时
} catch (Interrupted Exception e){}
output.println("用户名:"+Username+"");
}
}
因为我们所有的线程是同用一个servlet实例,那么我们所定义的实例变量肯定是只有一份。当一个用户输入的username=a,此时output这个实例变量将会储存着输入到浏览器里面的数据。由于当前线程睡眠。与此同时,另外的用户输入username=b。这样就会覆盖掉我们先前在output里面存储的用户的数据。才会出现了线程不安全的问题。
由于实例变量的不正确的使用导致线程不安全,怎样来保证我们的servlet是线程安全的呢?
1、实现 SingleThreadModel 接口
该接口指定了系统如何处理对同一个Servlet的调用。如果一个Servlet被这个接口指定,那么在这个Servlet中的service方法将不会有两个线程被同时执行(这种方法,可以,但是已经违背了线程安全的概念,线程安全强调的就是“多个线程同时访问”,但是如果一个Servlet实现了 SingleThreadModel接口,Servlet引擎将为每个新的请求创建一个单独的Servlet实例,这将引起大量的系统开销),当然也就不存在线程安全的问题。这种方法只要将前面的Concurrent Test类的类头定义更改为:
public class Concurrent Test extends HttpServlet implements SingleThreadModel {
…………
}
2、同步对共享数据的操作
使用synchronized 关键字能保证一次只有一个线程可以访问被保护的区段,在本论文中的Servlet可以通过同步块操作来保证线程的安全。同步后的代码如下:
…………
Public class Concurrent Test extends HttpServlet {
…………
username = request.getParameter ("username");
synchronized (this){ //针对使用synchronized关键字,我们要尽量缩小锁的粒度。否则会使多用户出现阻塞状态,影响系统性能。
output = response.getWriter ();
try {
Thread. Sleep (5000);
} Catch (Interrupted Exception e){}
output.println("用户名:"+Username+"
");
}
}
}
3、避免使用实例变量----最佳选择。
本实例中的线程安全问题是由实例变量造成的,只要在Servlet里面的任何方法里面都不使用实例变量,那么该Servlet就是线程安全的。
修正上面的Servlet代码,将实例变量改为局部变量实现同样的功能,代码如下:
……
public class Concurrent Test extends HttpServlet {
public void service (HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
PrintWriter output;
String username;
response.setContentType ("text/html; charset=gb2312");
……
}
}