servlet开发(二)之servlet的线程安全问题
之所以考虑线程安全问题,是因为引入了多线程。多线程指的是这个程序(一个进程)运行时产生了不止一个线程。如果不考虑多线程的话,程序执行只有一条路径,就像人在敲代码的时候只能敲代码,不能戴上耳机听歌。引入多线程,人在敲代码的时候还能听歌,解决一件事情的时候做了另一件事情,效率大大提高了(虽然敲代码的时候听歌不是为了听歌)。用多线程只有一个目的,那就是更好的利用cpu的资源。
Servlet/JSP技术和ASP、PHP等相比,由于其多线程运行而具有很高的执行效率。由于Servlet/JSP默认是以多线程模式执行的,所 以,在编写代码时需要非常细致地考虑多线程的安全性问题。
Servlet的多线程机制
Servlet体系结构是建立在Java多线程机制之上的,它的生命周期是由Web容器负责的。当客户端第一次请求某个Servlet时,Servlet容器将会根据web.xml配置文件实例化这个Servlet类。当有新的客户端请求该Servlet时,一般不会再实例化该Servlet类,也就是有多个线程在使用这个实例。Servlet容器会自动使用线程池等技术来支持系统的运行。
这样,当两个或多个线程同时访问同一个Servlet时,可能会发生多个线程同时访问同一资源的情况,数据可能会变得不一致。所以在用Servlet构建的Web应用时如果不注意线程安全的问题,会使所写的Servlet程序有难以发现的错误。
Servlet的线程安全问题
Servlet的线程安全问题主要是由于实例变量使用不当而引起的,这里以一个现实的例子来说明。
package MyServlet; import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; @WebServlet("/MyServletDemo")
public class MyServletDemo extends HttpServlet { PrintWriter output;
@Override
protected 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);
output.println("用户名:"+username+"<BR>");
} catch (Exception e) {
e.printStackTrace();
}
}
}
该Servlet中定义了一个实例变量output,在service方法将其赋值为用户的输出。当一个用户访问该Servlet时,程序会正常的运行,但当多个用户并发访问时,就可能会出现其它用户的信息显示在另外一些用户的浏览器上的问题。这是一个严重的问题。为了突出并发问题,便于测试、观察,我们在回显用户信息时执行了一个延时的操作。假设已在web.xml配置文件中注册了该Servlet,现有两个用户a和b同时访问该Servlet(可以启动两个IE浏览器,或者在两台机器上同时访问),即同时在浏览器中输入:
http://localhost:8081/MyWebProject/MyServletDemo?username=a
http://localhost:8081/MyWebProject/MyServletDemo?username=b
如果用户b比用户a回车的时间稍慢一点,将得到如下图所示的输出:
从上图可以看到,Web服务器启动了两个线程分别处理来自用户a和用户b的请求,但是在用户a的浏览器上却得到一个空白的屏幕,用户a的信息显示在用户b的浏览器上。该Servlet存在线程不安全问题。
分析一下为什么会出现这样的情况。
Java的内存模型JMM(Java Memory Model):JMM主要是为了规定了线程和内存之间的一些关系。根据JMM的设计,系统存在一个主内存(Main Memory),Java中所有实例变量都储存在主存中,对于所有线程都是共享的。每条线程都有自己的工作内存(Working Memory),工作内存由缓存和堆栈两部分组成,缓存中保存的是主存中变量的拷贝,缓存可能并不总和主存同步,也就是缓存中变量的修改可能没有立刻写到主存中;堆栈中保存的是线程的局部变量,线程之间无法相互直接访问堆栈中的变量。我们结合下图来分析当用户a和b的线程(简称为a线程、b线程)并发执行时,Servlet实例中所涉及变量的变化情况及线程的执行情况。
从上图可以清楚的看到,由于b线程对实例变量output的修改覆盖了a线程对实例变量output的修改,从而导致了用户a的信息显示在了用户b的浏览器上。如果在a线程执行输出语句时,b线程对output的修改还没有刷新到主存,那么将不会出现上上图所示的输出结果,因此这只是一种偶然现象,但这更增加了程序潜在的危险性。
设计线程安全的Servlet
针对Servlet的线程安全问题,Sun公司是提供有解决方案的:让Servlet去实现一个SingleThreadModel接口,如果某个Servlet实现了SingleThreadModel接口,那么Servlet引擎将以单线程模式来调用其service方法。
查看Sevlet的API可以看到,SingleThreadModel接口中没有定义任何方法和常量,在Java中,把没有定义任何方法和常量的接口称之为标记接口,经常看到的一个最典型的标记接口就是"Serializable",这个接口也是没有定义任何方法和常量的,标记接口在Java中有什么用呢?主要作用就是给某个对象打上一个标志,告诉JVM,这个对象可以做什么,比如实现了"Serializable"接口的类的对象就可以被序列化,还有一个"Cloneable"接口,这个也是一个标记接口,在默认情况下,Java中的对象是不允许被克隆的,就像现实生活中的人一样,不允许克隆,但是只要实现了"Cloneable"接口,那么对象就可以被克隆了。
让Servlet实现了SingleThreadModel接口,只要在Servlet类的定义中增加实现SingleThreadModel接口的声明即可。即类头定义为:
public class MyServletDemo extends HttpServlet implements SingleThreadModel{ }
SingleThreadModel不会解决所有的线程安全隐患。 例如,会话属性和静态变量仍然可以被多线程的多请求同时访问,即便使用了SingleThreadModel servlet。此外, 如果一个Servlet实现了SingleThreadModel接口,Servlet引擎将为每个新的请求创建一个单独的Servlet实例,这将引起大量的系统开销。建议开发人员应当采取其他手段来解决这些问题,而不是实现该接口,比如 避免实例变量的使用或者在访问资源时同步代码块。该接口在Servlet API 2.4中将不推荐使用。来看看在访问资源时同步代码块。
同步对共享数据的操作
使用synchronized 关键字能保证一次只有一个线程可以访问被保护的区段,在本论文中的Servlet可以通过同步块操作来保证线程的安全。
package MyServlet; import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; @WebServlet("/MyServletDemo")
public class MyServletDemo extends HttpServlet { PrintWriter output;
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String username;
response.setContentType("text/html;charset=gb2312");
username=request.getParameter("username");
synchronized (this) {
output=response.getWriter();
try {
//为了突出并发问题,在这设置一个延时
Thread.sleep(5000);
output.println("用户名:"+username+"<BR>");
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
再次按上述方式浏览时:
在程序中使用同步来保护要使用的共享的数据,也会使系统的性能大大下降。这是因为被同步的代码块在同一时刻只能有一个线程执行它,使得其同时处理客户请求的吞吐量降低,而且很多客户处于阻塞状态。在实际的开发中也应避免或最小化 Servlet 中的同步代码。
避免使用实例变量本实例中的线程安全问题是由实例变量造成的,只要在Servlet里面的任何方法里面都不使用实例变量,那么该Servlet就是线程安全的。修正上面的Servlet代码,将实例变量改为局部变量实现同样的功能。
package MyServlet; import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; @WebServlet("/MyServletDemo")
public class MyServletDemo extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
PrintWriter output;
String username;
response.setContentType("text/html;charset=gb2312");
username=request.getParameter("username");
synchronized(this){
output=response.getWriter();
try {
//为了突出并发问题,在这设置一个延时
Thread.sleep(5000);
output.println("用户名:"+username+"<BR>");
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
在Serlet中避免使用实例变量是保证Servlet线程安全的最佳选择。从Java 内存模型也可以知道,方法中的临时变量是在栈上分配空间,而且每个线程都有自己私有的栈空间,所以它们不会影响线程的安全。如果应用程序设计无法避免使用实例变量,那么使用同步来保护要使用的实例变量,但为保证系统的最佳性能,应该同步可用性最小的代码路径。
搜索微信公众号“程序员考拉”,关注更多精彩内容!
servlet开发(二)之servlet的线程安全问题的更多相关文章
- JavaWeb学习之Servlet(三)----Servlet的映射匹配问题、线程安全问题
[声明] 欢迎转载,但请保留文章原始出处→_→ 文章来源:http://www.cnblogs.com/smyhvae/p/4140529.html 一.Servlet映射匹配问题: 在第一篇文章中的 ...
- (转)JavaWeb学习之Servlet(三)----Servlet的映射匹配问题、线程安全问题
[声明] 欢迎转载,但请保留文章原始出处→_→ 文章来源:http://www.cnblogs.com/smyhvae/p/4140529.html 一.Servlet映射匹配问题: 在第一篇文章中的 ...
- javaweb学习之Servlet开发(二)
javaweb学习总结(六)--Servlet开发(二) 一.ServletConfig讲解 1.1.配置Servlet初始化参数 在Servlet的配置文件web.xml中,可以使用一个或多个< ...
- Servlet, Struts2和SpringMVC 并发访问线程安全问题
第一部分: Servlet不是线程安全的. 要解释Servlet为什么不是线程安全的,需要了解Servlet容器(即Tomcat)使如何响应HTTP请求的. 当Tomcat接收到Client的HTTP ...
- javaweb学习总结二十一(servlet开发入门、servlet生命周期以及调用过程)
一:servlet开发入门 servlet是sun公司一门开发动态web资源的技术,下面编写一个servlet入门程序: 1:在tomcat服务器webapps目录下新建firstServlet目录, ...
- JavaWeb学习之Servlet(二)----Servlet的生命周期、继承结构、修改Servlet模板
[声明] 欢迎转载,但请保留文章原始出处→_→ 文章来源:http://www.cnblogs.com/smyhvae/p/4140466.html 一.http协议回顾: 在上一篇文章中:JavaW ...
- (转)JavaWeb学习之Servlet(二)----Servlet的生命周期、继承结构、修改Servlet模板
[声明] 欢迎转载,但请保留文章原始出处→_→ 文章来源:http://www.cnblogs.com/smyhvae/p/4140466.html 一.http协议回顾: 在上一篇文章中:JavaW ...
- jsp/servlet学习二之servlet详解
Servlet API概览 Servlet API有一下四个java包: 1,javax.servlet,其中包含定义servlet和servlet容器之间契约的类和接口. 2,javax.servl ...
- java web学习总结(六) -------------------servlet开发(二)
一.ServletConfig讲解 1.1.配置Servlet初始化参数 在Servlet的配置文件web.xml中,可以使用一个或多个<init-param>标签为servlet配置一些 ...
- javaweb学习总结(六)——Servlet开发(二)
一.ServletConfig讲解 1.1.配置Servlet初始化参数 在Servlet的配置文件web.xml中,可以使用一个或多个<init-param>标签为servlet配置一些 ...
随机推荐
- kali linux之Bdfproxy
Bdfproxy(mitmproxy) 基于流量劫持动态注入shellcode(arp欺骗,dns欺骗,流氓ap) 步骤: 开启流量转发---------sysctl -w net.ipv4.ip_f ...
- 2019.2.14 t1 最大公约数
代码: #include <cstdio> #include <iostream> #include <cstring> #include <algorith ...
- 使用ceph-deploy进行ceph安装
ceph安装包介绍: 1.ceph-deploy: ceph的部署软件,通过该软件可以简便部署,这个软件并非整个ceph集群系统中必须的 2.ceph: ceph整个服务集群中的每个节点必须的软件.提 ...
- fetch网络请求 get 和 post
//在React Native中,使用fetch实现网络请求 /* fetch 是一个封装程度更高的网络API, 使用了Promise* Promise 是异步编程的一种解决方案* Promise 对 ...
- SimpleITK学习(一)基本概念
断断续续使用simpleitk处理CT和X光图片有些时间了,但是学的知识都比较零散,没有形成系统的概念,于是对着SimpleITK的英文文档https://simpleitk.readthedocs. ...
- JVM 统计监测命令
参考 深入理解JVM(七)——性能监控工具 JVM性能调优监控工具专题一 JVM调优总结 + jstat 分析 1. 进入 jdk 目录 cd /usr/local/jdk/bin 2. 查询所有 j ...
- PIE SDK加载自定义服务数据
1.功能简介 自定义服务数据,将符合要求的矢量数据和栅格数据集等数据以服务的方式发布,将数据存储在某服务器中,在有网络的情况下可以根据URL就可以访问,比较常见的服务数据类型的有ArcGIS Serv ...
- R 拆分EXCEL成多个文件
setwd("C:/Rworkfile") install.packages("readxl") library(readxl) www<-read_ex ...
- 小程序 - 分包加载上限8M(一)
使用分包 配置方法 假设支持分包的小程序目录结构如下 ├── app.js ├── app.json ├── app.wxss ├── packageA │ └── pages │ ├── c ...
- MySQL约束和修改数据表知识集结
一.约束 划分标准:功能.数据列的数目 功能: (1)NOT NULL(非空约束) (2)PRIMARY KEY(主键约束) (3)UNIQUE(唯一约束) (4)DEFAULT(默认约束) (5)F ...