[关闭]
@pastqing 2015-09-22T15:53:19.000000Z 字数 2896 阅读 2153

Servlet的线程安全性问题

java


Servlet的单实例多线程模式

首先谈谈Servlet的创建以及启动过程,当接收到访问某个Servlet的HTTP请求后, Servlet引擎(web容器)按照下面一个过程来调用一个Servlet:

  1. Servlet引擎检查是否已经装载并创建了该Serlvet实例, 如果装载并创建该Servlet实例则执行第4步, 否则执行第二步;

  2. 装载并创建一个Servlet实例。调用Servletinit()方法,进行实例创建以及初始化工作。这一步可以在web容器启动后,第一次访问该Servlet时完成, 也可以在启动容器时完成。

    • 在配置文件web.xml<servlet>的子标签<load-on-startup>用于指定Servlet的被装载时机和顺序。如果<load-on-startup>设置为0或者正整数,则指定该Servlet在web容器启动时就被实例化和调用它的init()方法。这个数字越小,该Servlet被装载的时间也越早,这样就可以自主定义Servlet的装载顺序了。<load-on-startup>设置为为负整数时,则由Servlet引擎决定该Servlet的装载时机

    • 在整个Servlet生命周期中,Servlet只实例化一次, 即init()方法只调用一次。因此是单实例模式,但这不是说Sevlet是单例模式

  3. 创建一个封装HTTP请求的HttpServletRequest对象和响应消息的HttpServletResponse对象,然后作为参数传递给service()方法。

  4. 当web容器重新启动或者停止之前,调用destory()方法以释放相关资源。

以上是Servlet的创建以及启动过程。上面说明了Servlet的单实例模式。Servlet采用多线程以提高效率, 在调用service()方法时,将有多个线程来同时操作,这样就会出现了线程安全问题。下面看一个例子:

  1. public class ThreadSafe extends HttpServlet {
  2. //定义一个实例变量count
  3. private int count;
  4. protected void doGet(
  5. HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {
  6. doPost(request, response);
  7. protected void doPost(
  8. HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {
  9. //输出10个count计数
  10. //输出当前线程名
  11. response.getWriter().println(Thread.currentThread().getName() + ": <br>");
  12. for(int i = 0; i < 10; i++) {
  13. response.getWriter().println("Counter = " + count + "<br>");
  14. try {
  15. Thread.sleep(1000);
  16. count++;
  17. } catch (InterruptedException exc) {}
  18. }
  19. }

我们前台用4个iframe同时访问该Servlet:

  1. <body>
  2. <table>
  3. <tr>
  4. <td><IFRAME src="./ThreadSafe?flag=1" name="servlet1"
  5. height="100%"> </IFRAME></td>
  6. <td><IFRAME src="./ThreadSafe?flag=2" name="servlet2"
  7. height="100%"> </IFRAME></td>
  8. <td><IFRAME src="./ThreadSafe?flag=3" name="servlet3"
  9. height="100%"> </IFRAME></td>
  10. <td><IFRAME src="./ThreadSafe?flag=4" name="servlet4"
  11. height="100%"> </IFRAME></td>
  12. </tr>
  13. </table>
  14. </body>

上面的测试代码是输出10个顺序计数,正确的输出结果应该是:

Counter = 0
Counter = 1
...

然而输出的结果是:
此处输入图片的描述
很明显是多线程出了问题, Servlet调用service方法时采用多线程,然而只有一个Servlet实例,因此出现问题。多线程同时对实例变量count进行读写操作。

如何解决Servlet的多线程安全问题

一、 避免在Servlet中使用实例变量。将实例变量本地化,采用局部变量保证线程安全。

二、 如果非要使用实例变量, 请使用synchrozied块进行同步。然而任何时候在系统中使用同步块都会对你的系统造成性能上的瓶颈。比如将代码修改成这样:

  1. public class ThreadSafe extends HttpServlet {
  2. //定义一个实例变量count
  3. private int count;
  4. private String mutex = "";
  5. protected void doGet(
  6. HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {
  7. doPost(request, response);
  8. protected void doPost(
  9. HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {
  10. //输出10个count计数
  11. //输出当前线程名
  12. response.getWriter().println(Thread.currentThread().getName() + ": <br>");
  13. //使用同步块
  14. synchrozied(mutex){
  15. for(int i = 0; i < 10; i++) {
  16. response.getWriter().println("Counter = " + count + "<br>");
  17. try {
  18. Thread.sleep(1000);
  19. count++;
  20. } catch (InterruptedException exc) {}
  21. }
  22. }
  23. }

三、 过时的方法,继承SingleThreadModel

  1. public class ThreadSafe extends HttpServlet implements SingleThreadModel{
  2. ...
  3. }

SingleThreadModel是一个空接口,它是一个标记接口。Servlet继承此接口后,将导致 Web 容器创建多个 servlet 实例;即为每个用户创建一个实例。对于任何大小的应用程序,这种实践都将导致严重的性能问题。

参考文献

Write thread-safe servlets

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注