zoukankan      html  css  js  c++  java
  • Servlet线程安全

    本文主要来源 zwchen的博客:http://zwchen.iteye.com/blog/91088

    概述
    在探讨java线程安全前,让我们先简要介绍一下Java语言。

    任何语言,如C++,C#,Java,它们都有相通之处,特别是语法,但如果有人问你,Java语言的核心是什么?类库?关键字?语法?似乎都不 是。Java语言的核心,也就是Sun始终不愿意开源的东西:Java虚拟机的实现(不过sun公开了其Java虚拟机规范),也就有了BEA的 JRockit,IBM的Jikes,Sun的Hotspot。

    Java的核心有两点,Java类加载(Java Class Loader)和Java内存管理,它们具体体现在Java类库的以下几个类:

    java.lang.ClassLoader(java.lang.Class):我们调用的类,包括其接口和超类,import的类是怎么被Java虚拟机载入的?为什么static的字段在servlet容器里面可以一直生存下去(Spring容器中)?

    java.lang.Thread(java.lang.ThreadLocal):垃圾回收是怎么进行的(垃圾回收线程)?我们的程序是怎么退出的?

    java.lang.refelect.Proxy(java.lang.refelect.Method):为什么Tomcat、 Tapestry、Webwork、Spring等容器和框架可以通过配置文件来调用我们写的类?Servlet规范、JSF规范、EJB规范、JDBC 规范究竟是怎么回事?为什么它们几乎都是一些接口,而不是具体类?


    Servlet线程安全

    在Java的server side开发过程中,线程安全(Thread Safe)是一个尤为突出的问题。因为容器,如Servlet、EJB等一般都是多线程运行的。虽然在开发过程中,我们一般不考虑这些问题,但诊断问题 (Robust),程序优化(Performance),我们必须深入它们。

    什么是线程安全?

    引用
    Thread-safe describes a program portion or routine that can be called from multiple programming threads without unwanted interaction between the threads。



    在Java里,线程安全一般体现在两个方面:

      1、多个thread对同一个java实例的访问(read和modify)不会相互干扰,它主要体现在关键字synchronized。如 ArrayList和Vector,HashMap和Hashtable(后者每个方法前都有synchronized关键字)。如果你在 interator一个List对象时,其它线程remove一个element,问题就出现了。

      2、每个线程都有自己的字段,而不会在多个线程之间共享。它主要体现在java.lang.ThreadLocal类,而没有Java关键字支持,如像static、transient那样。

    一个普遍的疑问,我们的Servlet中能够像JavaBean那样declare instance或static字段吗?如果不可以?会引发什么问题?

    答案是:不可以。我们下面以实例讲解:

    首先,我们写一个普通的Servlet,里面有instance字段count:

      web.xml  >>

    1 <servlet>
    2     <servlet-name>SimpleServlet</servlet-name>
    3     <servlet-class>servlet.SimpleServlet</servlet-class>
    4 </servlet>
    5 <servlet-mapping>
    6     <servlet-name>SimpleServlet</servlet-name>
    7     <url-pattern>/SimpleServlet</url-pattern>
    8 </servlet-mapping>

      SimpleServlet  >>

     1 public class SimpleServlet extends HttpServlet {
     2      private int counter = 0;  
     3      @Override
     4     protected void service(HttpServletRequest request, HttpServletResponse response)
     5             throws ServletException, IOException {
     6         response.getWriter().println("<HTML><BODY>");
     7         response.getWriter().println(this + " ==> ");
     8         response.getWriter().println(Thread.currentThread() + ": <br>"); 
     9         for(int c=0;c<10;c++){
    10             response.getWriter().println("Counter = " + counter + "<BR>");
    11             try {
    12                 Thread.sleep(1000);  
    13                 counter++;  
    14             } catch (Exception e) {
    15                 e.printStackTrace();
    16             }
    17         }
    18         response.getWriter().println("</BODY></HTML>");
    19     }
    20 }

      test.html  >>

    1 <HTML>  
    2     <BODY>  
    3         <TABLE>  
    4             <TR>  
    5                 <TD><IFRAME src="SimpleServlet" name="servlet1" height="200%"> </IFRAME></TD>  
    6             </TR>  
    7         </TABLE>  
    8     </BODY>  
    9 </HTML>  

       大家应该发现,test.html写的和zwchen的博客原文中的写的有点区别,本来也是按照zwchen的博客原文中的去测试的,但是相信很多人并没有得出理想的结果,正如博客下面评论上5楼所说的:“没有出现线程安全问题,数字的顺序都是正确的”,我也是如此(我用的是Firefox浏览器)。后来换了IE浏览器进行测试出现下面的问题,在页面上只显示出了第一个<tr></tr>里面的内容,于是我的处理方法就是:test.html的内容如上所示,打开3个IE浏览器,同时在浏览器中输入:

      a: http://localhost:8080/ServletTest/SimpleServlet
      b: http://localhost:8080/ServletTest/SimpleServlet

      c: http://localhost:8080/ServletTest/SimpleServlet 

       测试结果如下:

            

       我们会发现三点:

      1、Servlet是一个单例对象(Singleton),因为我们看到多次请求的this指针所有打印出来的hashCode值都相同。
      2、servlet在不同的线程(线程池)中运行,如http-8080-1,http-8080-2,http-8080-3 等输出值可以明显区分出不同的线程执行了不同一段Servlet逻辑代码。
      3、count变量在不同的线程中共享,而且它的值被不同的线程修改,输出时已经不是顺序输出。也就是说,其他的线程会篡改当前线程中实例变量的值,针对这些对象的访问不是线程安全的。


      上面的结果,违反了线程安全的两个方面。

      那么,我们怎样保证按照我们期望的结果运行呢?首先,我想保证产生的count都是顺序执行的。
      我们将Servlet代码重构如下:

     1 public class SimpleServlet extends HttpServlet {
     2      private int counter = 0;  
     3      private String mutex = ""; 
     4      @Override
     5     protected void service(HttpServletRequest request, HttpServletResponse response)
     6             throws ServletException, IOException {
     7         response.getWriter().println("<HTML><BODY>");
     8         response.getWriter().println(this + " ==> ");
     9         response.getWriter().println(Thread.currentThread() + ": <br>"); 
    10         synchronized (mutex){
    11             for(int c=0;c<10;c++){
    12                 response.getWriter().println("Counter = " + counter + "<BR>");
    13                 try {
    14                     Thread.sleep(1000);  
    15                     counter++;  
    16                 } catch (Exception e) {
    17                     e.printStackTrace();
    18                 }
    19             }
    20         }
    21         response.getWriter().println("</BODY></HTML>");
    22     }
    23 }

      这符合了我们的要求,输出都是按顺序的,这正式synchronized的含义。

      附带说一下,我现在synchronized的是一个字符串变量mutex,不是this对象,这主要是从performance和 Scalability考虑。Synchronized用在this对象上,会带来严重的可伸缩性的问题(Scalability),所有的并发请求都要排队!

  • 相关阅读:
    c# 一段生成6位不重复的随机数字码存8万个
    element ui 踩坑记
    Vue node.js 踩坑记
    javascript 异步回调链式调用 promise
    css 盒模型
    vue node.js 引入 linq
    Vue VsCode 项目 launch.json 文件
    node.js 基本语法识记
    Vue 2.0 入门示例识记
    在Windows系统中建立一个隐藏的帐户(在不登录界面显示)
  • 原文地址:https://www.cnblogs.com/gw811/p/2675294.html
Copyright © 2011-2022 走看看