`

JMM 与 servlet线程安全

    博客分类:
  • java
阅读更多

本文关键:

 

1.什么是JMM:

Java的内存模型JMM(Java Memory Model)JMM主要是为了规定了线程和内存之间的一些关系。根据JMM的设计,系统存在一个主内存(Main Memory),Java中所有实例变量都储存在主存中,对于所有线程都是共享的。每条线程都有自己的工作内存(Working Memory),工作内存由缓存和堆栈两部分组成,缓存中保存的是主存中变量的拷贝,缓存可能并不总和主存同步,也就是缓存中变量的修改可能没有立刻写到主存中;堆栈中保存的是线程的局部变量,线程之间无法相互直接访问堆栈中的变量。

 

2.servlet是多线程访问的,不要使用实例变量,改为局部变量。防止主内存数据被多线程修改。

 

 

原文:

 

2005-05-23 09:18作者:雷军环出处:计算机与信息技术

 

Servlet/JSP技术和ASP、PHP等相比,由于其多线程运行而具有很高的执行效率。由于Servlet/JSP默认是以多线程模式执行的,所以,在编写代码时需要非常细致地考虑多线程的安全性问题。然而,很多人编写Servlet/JSP程序时并没有注意到多线程安全性的问题,这往往造成编写的程序在少量用户访问时没有任何问题,而在并发用户上升到一定值时,就会经常出现一些莫明其妙的问题。

  Servlet的多线程机制
 
  Servlet体系结构是建立在Java多线程机制之上的,它的生命周期是由Web容器负责的。当客户端第一次请求某个Servlet时,Servlet容器将会根据web.xml配置文件实例化这个Servlet类。当有新的客户端请求该Servlet时,一般不会再实例化该Servlet类,也就是有多个线程在使用这个实例。Servlet容器会自动使用线程池等技术来支持系统的运行,如图1所示。


图1 Servlet线程池


  这样,当两个或多个线程同时访问同一个Servlet时,可能会发生多个线程同时访问同一资源的情况,数据可能会变得不一致。所以在用Servlet构建的Web应用时如果不注意线程安全的问题,会使所写的Servlet程序有难以发现的错误。

  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+"<BR>");
}
}


  该Servlet中定义了一个实例变量output,在service方法将其赋值为用户的输出。当一个用户访问该Servlet时,程序会正常的运行,但当多个用户并发访问时,就可能会出现其它用户的信息显示在另外一些用户的浏览器上的问题。这是一个严重的问题。为了突出并发问题,便于测试、观察,我们在回显用户信息时执行了一个延时的操作。假设已在web.xml配置文件中注册了该Servlet,现有两个用户a和b同时访问该Servlet(可以启动两个IE浏览器,或者在两台机器上同时访问),即同时在浏览器中输入:

  a: http://localhost: 8080/servlet/ConcurrentTest? Username=a

  b: http://localhost: 8080/servlet/ConcurrentTest? Username=b

  如果用户b比用户a回车的时间稍慢一点,将得到如图2所示的输出:


图2 a用户和b用户的浏览器输出


  从图2中可以看到,Web服务器启动了两个线程分别处理来自用户a和用户b的请求,但是在用户a的浏览器上却得到一个空白的屏幕,用户a的信息显示在用户b的浏览器上。该Servlet存在线程不安全问题。下面我们就从分析该实例的内存模型入手,观察不同时刻实例变量output的值来分析使该Servlet线程不安全的原因。

  Java的内存模型JMM(Java Memory Model)JMM主要是为了规定了线程和内存之间的一些关系。根据JMM的设计,系统存在一个主内存(Main Memory),Java中所有实例变量都储存在主存中,对于所有线程都是共享的。每条线程都有自己的工作内存(Working Memory),工作内存由缓存和堆栈两部分组成,缓存中保存的是主存中变量的拷贝,缓存可能并不总和主存同步,也就是缓存中变量的修改可能没有立刻写到主存中;堆栈中保存的是线程的局部变量,线程之间无法相互直接访问堆栈中的变量。根据JMM,我们可以将论文中所讨论的Servlet实例的内存模型抽象为图3所示的模型。


图3 Servlet实例的JMM模型


  下面根据图3所示的内存模型,来分析当用户a和b的线程(简称为a线程、b线程)并发执行时,Servlet实例中所涉及变量的变化情况及线程的执行情况,如图4所示。

调度时刻 a线程 b线程
T1 访问Servlet页面  
T2   访问Servlet页面
T3 output=a的输出username=a休眠5000毫秒,让出CPU  
T4   output=b的输出(写回主存)username=b休眠5000毫秒,让出CPU
T5 在用户b的浏览器上输出a线程的username的值,a线程终止。  
T6   在用户b的浏览器上输出b线程的username的值,b线程终止。

                  图4 Servlet实例的线程调度情况

  从图4中可以清楚的看到,由于b线程对实例变量output的修改覆盖了a线程对实例变量output的修改,从而导致了用户a的信息显示在了用户b的浏览器上。如果在a线程执行输出语句时,b线程对output的修改还没有刷新到主存,那么将不会出现图2所示的输出结果,因此这只是一种偶然现象,但这更增加了程序潜在的危险性。

 设计线程安全的Servlet

  通过上面的分析,我们知道了实例变量不正确的使用是造成Servlet线程不安全的主要原因。下面针对该问题给出了三种解决方案并对方案的选取给出了一些参考性的建议。

  1、实现 SingleThreadModel 接口

  该接口指定了系统如何处理对同一个Servlet的调用。如果一个Servlet被这个接口指定,那么在这个Servlet中的service方法将不会有两个线程被同时执行,当然也就不存在线程安全的问题。这种方法只要将前面的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){
Output = response.getWriter ();
Try {
Thread. Sleep (5000);
} Catch (Interrupted Exception e){}
output.println("用户名:"+Username+"<BR>");
}
}
}

  3、避免使用实例变量

  本实例中的线程安全问题是由实例变量造成的,只要在Servlet里面的任何方法里面都不使用实例变量,那么该Servlet就是线程安全的。

  修正上面的Servlet代码,将实例变量改为局部变量实现同样的功能,代码如下:

……
Public class Concurrent Test extends HttpServlet {public void service (HttpServletRequest request, HttpServletResponse Response) throws ServletException, IOException {


Print Writer output;
String username;
Response.setContentType ("text/html; charset=gb2312");
……

}
}


  对上面的三种方法进行测试,可以表明用它们都能设计出线程安全的Servlet程序。但是,如果一个Servlet实现了SingleThreadModel接口,Servlet引擎将为每个新的请求创建一个单独的Servlet实例,这将引起大量的系统开销。SingleThreadModel在Servlet2.4中已不再提倡使用;同样如果在程序中使用同步来保护要使用的共享的数据,也会使系统的性能大大下降。这是因为被同步的代码块在同一时刻只能有一个线程执行它,使得其同时处理客户请求的吞吐量降低,而且很多客户处于阻塞状态。另外为保证主存内容和线程的工作内存中的数据的一致性,要频繁地刷新缓存,这也会大大地影响系统的性能。所以在实际的开发中也应避免或最小化 Servlet 中的同步代码;在Serlet中避免使用实例变量是保证Servlet线程安全的最佳选择。从Java 内存模型也可以知道,方法中的临时变量是在栈上分配空间,而且每个线程都有自己私有的栈空间,所以它们不会影响线程的安全。

  小结

  Servlet的线程安全问题只有在大量的并发访问时才会显现出来,并且很难发现,因此在编写Servlet程序时要特别注意。线程安全问题主要是由实例变量造成的,因此在Servlet中应避免使用实例变量。如果应用程序设计无法避免使用实例变量,那么使用同步来保护要使用的实例变量,但为保证系统的最佳性能,应该同步可用性最小的代码路径。

分享到:
评论

相关推荐

    servlet与Struts action线程安全问题分析

    Servlet和Struts Action是两种常见的Java Web开发组件,它们在多线程环境下运行时可能存在线程安全问题。线程安全是指在多线程环境中,一个类或者方法能够正确处理多个线程的并发访问,保证数据的一致性和完整性。 ...

    深入研究Servlet线程安全性问题.pdf

    ### 深入研究Servlet线程安全性问题 #### 一、引言 Servlet技术作为Java Web开发中的核心组件之一,因其高效性和灵活性被广泛应用于Web应用程序的开发中。Servlet能够处理HTTP请求,并产生相应的响应。它的一个...

    深入研究Servlet线程安全性问题

    Servlet线程安全性问题是一个在Java Web开发中至关重要的议题,特别是在高并发环境下。Servlet作为Java服务器端组件,其默认的多线程模型使得一个Servlet实例可能会被多个客户端请求同时调用,这就要求开发者对线程...

    servlet与Struts action线程安全问题分析(pdf)

    #### 三、Servlet线程安全问题 线程安全问题主要发生在Servlet中使用实例变量时。当多个线程并发访问同一个Servlet实例时,如果这些线程试图同时修改实例变量,就可能发生数据不一致的情况。以下通过一个具体的示例...

    JavaEE学习[JavaSE-06多线程.rar]

    此外,对Java内存模型(JMM)的理解也是很重要的,它规定了线程如何共享和访问内存,以及数据一致性如何保证。 通过分析这个压缩包,你可以系统地学习和实践多线程编程,从而为后续的JavaEE开发打下坚实的基础。在...

    java并发笔记

    - 如多数Servlet可以通过设计成无状态的方式来减少线程安全性的负担。 - **使用AtomicLong管理计数器状态**: - `count++`操作涉及到读取、修改和写回三个步骤,在多线程环境中不是原子操作,容易引发数据不一致性...

    Java面试题集 收集的

    3. **多线程与并发**: - 线程的状态转换与同步机制 - volatile关键字的作用 - synchronized的使用及原理 - wait()、notify()、notifyAll()的理解与使用 - Java内存模型(JMM)和线程通信 - Callable与Future...

    整理的2018 java面试汇总,要面试的抓紧看了

    #### 二、多线程与并发控制 **1.3 多线程基础知识** - **CountDownLatch、CyclicBarrier、Semaphore与Exchanger的工作原理** - `CountDownLatch`:用于等待一组事件发生。 - `CyclicBarrier`:用于等待一组线程...

    实战Java高并发程序设计模式高清视频教程.zip

    同时,我们还将探讨线程同步和互斥机制,包括synchronized关键字、volatile变量、Lock接口以及ReentrantLock等,这些都是防止数据不一致性和确保线程安全的关键。 在设计模式方面,我们会学习到一些经典的并发模式...

    tomcat优化资料

    了解JMM有助于我们理解内存可见性、volatile、synchronized等关键字的工作原理,从而编写出更高效、线程安全的代码。 1. **线程之间的通信**:理解volatile的内存语义,它能确保变量在所有线程间可见,但不保证原子...

    java高并发程序设计资料

    3. **Java并行程序基础**:理解线程的创建、调度、同步与通信是并发编程的基础。Java提供Thread类和ExecutorService框架来创建和管理线程,使用wait/notify、Semaphore、CyclicBarrier等机制进行同步。 4. **无锁...

    飞秋java源码-interviewNote:面试笔记

    线程机制、线程通信、J.U.C组件、JMM、线程安全、锁优化 Java I/O  磁盘操作、字节操作、字符操作、对象操作、网络操作、NIO Java 虚拟机  运行时数据区域、垃圾收集、内存分配机制、类加载机制、性能调优监控工具...

    一线互联网企业面试题.pdf

    9. **Java内存模型与垃圾回收**:Java内存模型JMM规定了线程如何访问共享变量,垃圾回收机制主要通过可达性分析算法识别无用对象并进行回收。 10. **Integer引用交换**:两个Integer引用在swap方法内交换不会改变...

    JavaWeb全栈面试题

    5. **线程管理**:线程是Java并发处理的基础,面试时可能会讨论到线程的创建与启动、同步与通信(synchronized、Lock、Semaphore等)、线程池(ExecutorService、ThreadPoolExecutor)以及死锁、活锁、饥饿现象等。...

    java8集合源码分析-LearningNotes:Java笔记

    组件、JMM、线程安全、锁优化 磁盘操作、字节操作、字符操作、对象操作、网络操作、NIO 运行时数据区域、垃圾收集、内存分配机制、类加载机制、性能调优监控工具 Java 常见的 10 余种设计模式,全 23 种设计模式逐步...

    java面试自创避邪宝典

    - **并发编程**:线程安全、锁机制、并发工具类(如Semaphore、ExecutorService)的使用,以及Java内存模型(JMM)的理解。 - **设计模式**:工厂模式、单例模式、观察者模式、装饰器模式等经典设计模式的运用。 ...

    java复习总结,含编写习惯,教程开发

    Java是一种广泛使用的面向对象的编程语言,以其跨平台性、高效性和安全性著称。这篇复习总结将深入探讨Java的核心概念,编程习惯以及实际开发过程中的关键知识点。 首先,"Java编写习惯"是成为一名优秀Java程序员的...

    工作需要1

    - Java内存模型(JMM)定义了线程如何访问共享内存,以及如何避免数据竞争和一致性问题。 - 自旋锁是一种等待机制,线程会不断地检查锁是否可用,直到获得锁为止,适用于锁的持有时间短的情况。 以上是根据提供的...

    java面试大全.rar

    并发编程是Java的一个强项,Java内存模型(JMM)、线程同步(synchronized、Lock)、线程池(ExecutorService)、并发容器(ConcurrentHashMap、CopyOnWriteArrayList等)等都是面试中会讨论的内容。 数据库方面,...

    实习或秋招校招-思维导图总结

    - JSP:理解JSP基本语法、指令和隐式对象,以及与Servlet的交互。 - Servlet:学习Servlet生命周期、请求响应处理,以及Filter和Listener的应用。 - EJB:探讨企业级Java组件,包括Session Bean、Message Driven ...

Global site tag (gtag.js) - Google Analytics