Servlet线程安全问题
Servlet采用单实例多线程方式运行,因此是线程不安全的。默认情况下,非分布式系统,Servlet容器只会维护一个Servlet的实例,当多个请求到达同一个Servlet时,Servlet容器会启动多个线程分配给不同请求来执行同一个Servlet实例中的服务方法。为什么这么做?有效利用JVM允许多个线程访问同一个实例的特性,来提高服务器性能。因为,无论是同步线程对Servlet的调用,还是为每一个线程初始化一个Servlet实例,都会带来巨大的性能问题。这也就是为什么Servlet会存在多线程安全问题。
一个Servlet对应多个URL映射,将会生成多个Servlet实例。如下所示:
输出结果:
输出结果可以看到映射/demoServlet1和/demoServlet2对应Servlet实例是不同的。
结果证明:Servlet将为每一个URL映射生成一个实例;一个Servlet可能存在多个示例,但每一个实例都会对应不同的URL映射。
一、Servlet处理多个请求访问过程
Servlet容器默认是采用单实例多线程的方式处理多个请求的。
1、当web服务器启动的时候(或客户端发送请求到服务器时),Servlet就被加载并实例化(只存在一个Servlet实例);
2、容器初始化化Servlet主要就是读取配置文件(例如tomcat,可以通过servlet.xml的<Connector>设置线程池中线程数目,初始化线程池通过web.xml,初始化每个参数值等等。
3、当请求到达时,Servlet容器通过调度线程(Dispatchaer Thread) 调度它管理下线程池中等待执行的线程(Worker Thread)给请求者;
4、线程执行Servlet的service方法;
5、请求结束,放回线程池,等待被调用;(注意:避免使用实例变量(成员变量),因为如果存在成员变量,可能发生多线程同时访问该资源时,都来操作它,照成数据的不一致,因此产生线程安全问题)
从上面可以看出:
第一:Servlet单实例,减少了产生servlet的开销;
第二:通过线程池来响应多个请求,提高了请求的响应时间;
第三:Servlet容器并不关心到达的Servlet请求访问的是否是同一个Servlet还是另一个Servlet,直接分配给它一个新的线程;如果是同一个Servlet的多个请求,那么Servlet的service方法将在多线程中并发的执行;
第四:每一个请求由ServletRequest对象来接受请求,由ServletResponse对象来响应该请求;
当容器收到一个Servlet请求,Dispatcher线程从线程池中选出一个工作组线程,将请求传递给该线程,然后由该线程来执行Servlet的service方法。 当这个线程正在执行的时候,容器收到另一个请求,调度者线程将从线程池中选出另外一个工作组线程来服务则个新的请求,容器并不关心这个请求是否访问的是同一个Servlet还是另一个 Servlet。当容器收到对同一个Servlet的多个请求的时候,那这个servlet的service方法将在多线程中并发的执行。
二、设计线程安全的Servlet
下面讨论单个Servlet、多线程情况下保证数据线程同步的几个方法。
1、synchronized:代码块,方法。大家都会使用的方式,不用详细介绍了。 建议优先选择修饰方法。
2、volatile 轻量级的锁,可以保证多线程情况单线程读取所修饰变量时将会强制从共享内存中读取最新值,但赋值操作并非原子性。
一个具有简单计数功能Servlet示范:
/**
* 使用Volatile作为轻量级锁作为计数器
*
* @author yongboy
* @date 2011-3-12
* @version 1.0
*/
@WebServlet("/volatileCountDemo")
public class VolatileCountServlet extends HttpServlet {
private static final long serialVersionUID = 1L; private volatile int num = 0; protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
addOne();
response.getWriter().write("now access num : " + getNum());
} /**
* 读取开销低
*/
private int getNum() {
return num;
} /**
* 其写入为非线程安全的,赋值操作开销高
*/
private synchronized void addOne() {
num ++;
}
}
我们在为volatile修饰属性赋值时,还是加把锁的。
3、ThreadLocal 可以保证每一个线程都可以独享一份变量副本,每个线程可以独立改变副本,不会影响到其它线程。
这里假设多线程环境一个可能落显无聊的示范,初始化一个计数,然后循环输出:
@WebServlet("/threadLocalServlet")
public class ThreadLocalServlet extends HttpServlet {
private static final long serialVersionUID = 1L; private static ThreadLocal threadLocal = new ThreadLocal() {
@Override
protected Integer initialValue() {
return 0;
}
}; protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
response.setHeader("Connection", "Keep-Alive"); PrintWriter out = response.getWriter();
out.println("start... " + " [" + Thread.currentThread() + "]");
out.flush(); for (int i = 0; i < 20; i++) {
out.println(threadLocal.get());
out.flush(); threadLocal.set(threadLocal.get() + 1);
} // 手动清理,当然随着当前线程结束,亦会自动清理调
threadLocal.remove();
out.println("finish... ");
out.flush();
out.close();
}
}
若创建一个对象较为昂贵,但又是非线程安全的,在某种情况下要求仅仅需要在线程中独立变化,不会影响到其它线程。选择使用ThreadLocal较好一些,嗯,还有,其内部使用到了WeakHashMap,弱引用,当前线程结束,意味着创建的对象副本也会被垃圾回收。 Hibernate使用ThreadLocal创建Session;Spring亦用于创建对象会使用到一点。
请注意这不是解决多线程共享变量的钥匙,甚至你想让某个属性或对象在所有线程中都保持原子性,显然这不是解决方案。
4、Lock 没什么好说的,现在JDK版本支持显式的加锁,相比synchronized,添加与释放更加灵活,功能更为全面。
@WebServlet("/lockServlet")
public class LockServlet extends HttpServlet {
private static final long serialVersionUID = 1L; private static int num = 0;
private static final Lock lock = new ReentrantLock(); protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
try{
lock.lock();
num ++;
response.getWriter().println(num);
}finally{
lock.unlock();
}
}
}
必须手动释放锁,否则将会一直锁定。
5、wait/notify 较老的线程线程同步方案,较之Lock,不建议再次使用。
6、原子操作
原子包装类,包括一些基本类型(int, long, double, boolean等)的包装,对象属性的包装等。
@WebServlet("/atomicServlet")
public class AtomicServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private static final AtomicInteger num = new AtomicInteger(0); protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
PrintWriter out = response.getWriter();
out.println(num.incrementAndGet());
out.flush();
out.close();
}
}
包装类提供了很多的快捷方法,比如上面的incrementAndGet方法,自身增加1,然后返回结果值,并且还是线程安全的,省缺了我们很多手动、笨拙的编码实现
7、一些建议
尽量不要在Servlet中单独启用线程;
使用尽可能使用局部变量;
尽可能避免使用锁;
属性的线程安全:ServletContext,HttpSession,ServletRequest对象中属性。
1、ServletContext:(线程不安全)
ServletContext是可以多线程同时读/写属性的,线程是不安全的。要对属性的读写进行同步处理或者进行深度Clone()。所以在Servlet上下文中尽可能少量保存会被修改(写)的数据,可以采取其他方式在多个Servlet中共享,比方我们可以使用单例模式来处理共享数据。
2、HttpSession:(线程不安全)
HttpSession对象在用户会话期间存在,只能在处理属于同一个Session的请求的线程中被访问,因此Session对象的属性访问理论上是线程安全的。
当用户打开多个同属于一个进程的浏览器窗口,在这些窗口的访问属于同一个Session,会出现多次请求,需要多个工作线程来处理请求,可能造成同时多线程读写属性。这时我们需要对属性的读写进行同步处理:使用同步块Synchronized和使用读/写器来解决。
3、ServletRequest:(线程安全)
对于每一个请求,由一个工作线程来执行,都会创建有一个新的ServletRequest对象,所以ServletRequest对象只能在一个线程中被访问。ServletRequest是线程安全的。注意:ServletRequest对象在service方法的范围内是有效的,不要试图在service方法结束后仍然保存请求对象的引用。
Servlet线程安全问题的更多相关文章
- javaweb回顾第六篇谈一谈Servlet线程安全问题
前言:前面说了很多关于Servlet的一些基础知识,这一篇主要说一下关于Servlet的线程安全问题. 1:多线程的Servlet模型 要想弄清Servlet线程安全我们必须先要明白Servlet实例 ...
- Servlet线程安全问题(转载)
转载地址:https://www.cnblogs.com/LipeiNet/p/5699944.html 前言:前面说了很多关于Servlet的一些基础知识,这一篇主要说一下关于Servlet的线程安 ...
- JAVAEE_Servlet_18_关于Servlet线程安全问题
关于Servlet线程安全问题 Servlet线程安全 Servlet 是单实例多线程的环境下运行的. 在服务器运行期间,一个Servlet接口实现类,只能创建一个实例对象(一个进程(Servlet接 ...
- (2.1)servlet线程安全问题
本文参考链接:http://www.yesky.com/334/1951334.shtml 摘 要:介绍了Servlet多线程机制,通过一个实例并结合Java 的内存模型说明引起Servlet线程不安 ...
- 开玩笑Web它servlet(五岁以下儿童)---- 如何解决servlet线程安全问题
servlet默认值是安全线的存在,但说白,servlet安全线实际上是一个多线程线程安全问题.因为servlet它正好是一个多线程的安全问题出现. 每次通过浏览器http同意提交请求,将一个实例se ...
- IT兄弟连 JavaWeb教程 Servlet线程安全问题
在Internet中,一个Web应用可能被来自西面八方的客户并发访问(即同时访问),而且有可能这些客户并发访问的是Web应用中的同一个Servlet,Servlet容器为了保证能同时相应多个客户端要求 ...
- javaweb学习总结二十三(servlet开发之线程安全问题)
一:servlet线程安全问题发生的条件 如果多个客户端访问同一个servlet时,发生线程安全问题,那么它们访问的是相同的资源.如果访问 的不是相同资源,则不存在线程安全问题. 实例1:不会产生线程 ...
- JavaWeb学习之Servlet(三)----Servlet的映射匹配问题、线程安全问题
[声明] 欢迎转载,但请保留文章原始出处→_→ 文章来源:http://www.cnblogs.com/smyhvae/p/4140529.html 一.Servlet映射匹配问题: 在第一篇文章中的 ...
- 玩转Web之servlet(五)---- 怎样解决servlet的线程安全问题
servlet默认是存在线程安全问题的,但是说白了,servlet的线程安全问题实际上就是多线程的线程安全问题,因为servlet恰巧是一个多线程才会出现安全性问题. 浏览器每次通过http协议去提交 ...
随机推荐
- linux 安装mongodb
Linux 安装mongodb 1.下载mongodb linux wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-amazon- ...
- jquery chart plugin
jquery flot http://www.jqueryflottutorial.com/ jquery jqplot http://www.jqplot.com/ highcharts中文网 : ...
- java继承和多态
父类和子类 如果类C1扩展自另一个类C2,那么C1称为子类或派生类,C2称为父类或基类.派生类可以从它的基类中继承可访问的数据域和方法,还可添加新数据域和新方法 例如:实现一个几何图形基类; clas ...
- 在Visual Studio中利用NTVS创建Pomelo项目
刚看新闻,才知道微软发布了Node.js Tools for Visual Studio(NTVS),受够了WebStorm输入法Bug的困扰,这下终于可以解脱了.以Pomelo为例,运行命令:pom ...
- nodejs学习:sails框架的学习
上周通过搭建CMS系统接触到了sails框架,知道一些ORM的概念.这周开始深入后台数据交互,发现twenty框架的数据结构在sails上又设计了一番(比如node.category),不得不说师哥就 ...
- linux 流量监控
iftop -i p5p1 -n -p dstat -n
- Modbus Poll master-slave测试 Dtech USB转485(worldsing 笔记)
1,简介 网站地址:http://www.modbustools.com/ 该网站提供了几个软件工具,可以运行于windows 2000/XP/Vista/7环境下,用来测试和仿真Modebus设备. ...
- C#程序实现动态调用DLL的研究
摘 要:在<csdn开发高手>2004年第03期中的<化功大法——将DLL嵌入EXE>一文,介绍了如何把一个动态链接库作为一个资源嵌入到可执行文件,在可执行文件运行时,自动从资 ...
- 剑指OFFER之从二叉搜索树的后序遍历序列(九度OJ1367)
题目描述: 输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果.如果是则输出Yes,否则输出No.假设输入的数组的任意两个数字都互不相同. 输入: 每个测试案例包括2行: 第一行为1个整数 ...
- 剑指OFFER之包含min函数的栈(九度OJ1522)
题目描述: 定义栈的数据结构,请在该类型中实现一个能够得到栈最小元素的min函数. 输入: 输入可能包含多个测试样例,输入以EOF结束. 对于每个测试案例,输入的第一行为一个整数n(1<=n&l ...