Servlet线程安全问题(转载)
转载地址:https://www.cnblogs.com/LipeiNet/p/5699944.html
前言:前面说了很多关于Servlet的一些基础知识,这一篇主要说一下关于Servlet的线程安全问题。
1:多线程的Servlet模型
要想弄清Servlet线程安全我们必须先要明白Servlet实例是如何创建,它的模式是什么样的。
在默认的情况下Servlet容器对声明的Servlet,只创建一个Servlet实例,那么如果要是多个客户同时请求访问这个Servlet,Servlet容器就采取多线程。下面我们来看一幅图
从图中可以看出当客户发送请求的时候,Servlet容器通过调度者线程从线程池中选择一个线程,然后将请求传递给这个线程,然后在由这个线程去执行Servlet的Service方法。
如果多个客户端同时请求执行一个Servlet实例,那么这个Servlet容器的Service方法将在多个线程中并发执行(比喻图中客户1,客户2,客户3同时调用Servlet1实例,那么调度者线程就会在线程池中调用3个线程分别用于客户1,2,3的请求,然后3个线程同时并发执行Servlet1实例的Service方法)因为Servlet容器采取的单实例多线程的方法,那么就大大的减小了Servlet实例创建的开销,提升了对请求的响应时间,也是这样引起了Servlet线程安全问题。所以我们下面说线程安全问题。
2:Servlet的线程安全
2.1:变量的线程安全
2.1.1:变量为啥会存在线程安全
我们先看一段代码
1 public class HelloWorldServlet extends HttpServlet{
2 private String userName;
3 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException
4 {
5 userName=request.getParameter("userName");
6 PrintWriter out=response.getWriter();
7 if(userName!=null&&userName!="")
8 {
9 out.print(userName);
10 }
11 else {
12 out.println("用户名不存在");
13 }
14 }
15 }
我们来分析这段代码,现在有A,B2个客户端同时请求HelloWorldServlet 这个实例,Servlet容器分配线程T1来服务A客户端的请求,T2来服务B客户端的请求,操作系统首先调用T1来运行,T1运行到第6行的时候得到了用户名为张三并保存,此时时间片段到了,操作系统开始调用T2运行也运行到第6行但是这个用户名是李四,此时时间片段又到了,操作系统又开始运行T1,从第7行开始运行,但是此时的用户名却成了李四,输出的时候确实李四(很明显是错误的),那么这个时候就出现了线程安全问题。
2.1.2:如何防止变量的线程安全
- 把全局变量改为局部变量
public class myservlet2 extends HttpServlet{
int num=1;
//servlet->GenericServlet->HttpServlet
@Override
public void init() throws ServletException {
// TODO Auto-generated method stub
super.init();
System.out.println("HttpServlet...init...初始化");
}@Override
public void service(ServletRequest arg0, ServletResponse arg1)
throws ServletException, IOException {
// TODO Auto-generated method stub
num++;
System.out.println(num);
System.out.println(arg0.getRemoteAddr());
System.out.println("HttpServlet...servlet demo2");
}@Override
public void destroy() {
// TODO Auto-generated method stub
super.destroy();
System.out.println("HttpServlet...destory...结束");
}}
因为每次调用这个方法的时候就会重写对userName实例化这样一来就不会存在线程安全问题了。
- 使用synchronized对doGet进行同步
protected synchronized void doGet(HttpServletRequest request, HttpServletResponse response)
采用这种方式明显不合适,因为这样T2必须要等T1执行完毕以后才可以执行,大大的影响了效率。
3.如果是静态资源则加上final表示这个资源不可以改变
比喻 final static String url="jdbc:mysql://localhost:3306/blog";
2.2:属性的线程安全
在Servlet中可以访问保存在ServletContext,HttpSession,ServletRequest对象中的属性,这三种对象都提供了getAttribute(),setAttribute() 方法用来对取和设置属性,那么这三个不同范围对象的属性访问是否线程安全呢,下面我们来一起看一下
2.2.1:ServletContext
首先明确一点是ServletContext是被应用程序下所有的Servlet所共享的,那么ServletContext对象就可以被web应用程序所有的Servlet访问,那么这样一来多个Servlet就可以同时对ServletContext的属性进行设置和访问,所以这个时候就会出现线程安全问题。我们来看一段代码
1 protected void service(HttpServletRequest request, HttpServletResponse response)
2 {
3 String userName=request.getParameter("userName");
4 if ("login") {
5 List list=(List)getServletContext().getAttribute("userList");
6 list.add(userName);
7 }
8 else {
9 List list=(List)getServletContext().getAttribute("userList");
10 list.remove(userName);
11 }
12 }
1 protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException
2 {
3 List list=(List)getServletContext().getAttribute("userList");
4 int count=list.size();
5 for(int i=0;i<count;i++)
6 {
7 PrintWriter out=response.getWriter();
8 out.println(list.get(i));
9 }
10 }
第一段代码是当用户登录以后把用户名保存在ServletContext属性中,如果不是登录就删除这个用户
第二段代码就是查看应用程序所有的用户登录情况,那么我们看如何出现线程安全问题的
当2个请求并发执行的时候,可能第二段代码刚刚执行第五行的时候获取的count=5;但是呢另一个请求恰好执行第一段代码第十行,把其中的某个用户删除了,当第二段代码在循环遍历的时候运行到count=5的时候就会数组超过索性界限异常。那么此时就出现了线程安全问题。那么遇到这样的问题怎么解决呢,第一就是把ServletContext属性值进行拷贝保存起来,第二就是采用synchronized 进行同步(这个效率低)
2.2.2:HttpSession
httpSession对象在用户会话期间存活的,不像ServletContext一样被所有的用户共享,所以说一个HttpSession在同一个时刻只用一个用户进行请求的,因此理论看来Session是线程安全的,其实并不是如此,这个和浏览器有关,在上一篇Session我们说过,同一个浏览器只能具有一个Session,那么这样一来就会出现Session线程安全问题,看如下代码
1 protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException
2 {
3 String commandType=request.getParameter("commandType");
4 HttpSession session=request.getSession();
5 List list=(List)session.getAttribute("items");
6 if ("add".equals(commandType)) {
7 //添加
8 }
9 else if("delete".equals(commandType)){
10 //删除
11 }
12 else {
13 int count=list.size();
14 for (int i = 0; i < count; i++) {
15 //遍历
16 }
17 }
18 }
上面是一个添加物品信息的一个简单伪代码,如果用户现在在一个浏览器窗口删除一件物品的同时又在另一个窗口去获取所有的物品这个时候就会出现线程安全,从上面的介绍得知Servlet容器是多线程单实例的,这个时候Servlet容器就会分配2个线程来分别为删除物品和获取所有物品进行服务,如果其中一个线程刚好运行到14行时间片段结束,另一个线程这个时候又运行第10行删除一条物品信息,然后第一个线程又开始运行第15开始遍历,此时同样出现了上面数组索性超出范围的错误。
2.2.3:HttpRequest
httprequest是线程安全的,因为每个请求都会调用Service,都会创建一个新的HttpRequest和局部变量一样。
3:SingleThreadModel
从名字很好理解,就是单线程模式,也就是说如果Servlet实现了SingleThreadModel接口,Servlet容器就保证一个时刻只有一个线程在Servlet实例的Service方法运行(其实和同步差不多)这样一来就很影响效率了,现在SingleThreadModel已经被废弃了,值得注意的是就算Servlet实现了SingleThreadModel接口并不一定保证线程安全,比喻上面说的ServletContext,HttpSession,因为ServletContext是应用程序共享的,可能2个Servlet实例同时运行造成线程安全,HttpSession因为是在同一浏览器共享的所以也会出现(虽然可能性很小)
4:总结
1:只要我们了解Servlet容器工作的模式,可能就能够理解为什么Servlet会出现线程安全问题,所以一定牢记Servlet容器是多线程单实例的模型
2:避免使用全局变量,最好是使用局部变量,其实这本身也是一个好的编程习惯
3:应该使用只读的实例变量和静态变量(就是前面加上final意为不可改变)
4:不要在Servlet上自己创建线程,因为Servlet容器已经帮我们做好了。
5:如果要修改共享对象的时候记得要同步,尽量缩小同步的范围(比喻修改Session时候直接使用synchronized(Session)即可),避免影响性能
Servlet线程安全问题(转载)的更多相关文章
- javaweb回顾第六篇谈一谈Servlet线程安全问题
前言:前面说了很多关于Servlet的一些基础知识,这一篇主要说一下关于Servlet的线程安全问题. 1:多线程的Servlet模型 要想弄清Servlet线程安全我们必须先要明白Servlet实例 ...
- JAVAEE_Servlet_18_关于Servlet线程安全问题
关于Servlet线程安全问题 Servlet线程安全 Servlet 是单实例多线程的环境下运行的. 在服务器运行期间,一个Servlet接口实现类,只能创建一个实例对象(一个进程(Servlet接 ...
- 开玩笑Web它servlet(五岁以下儿童)---- 如何解决servlet线程安全问题
servlet默认值是安全线的存在,但说白,servlet安全线实际上是一个多线程线程安全问题.因为servlet它正好是一个多线程的安全问题出现. 每次通过浏览器http同意提交请求,将一个实例se ...
- Servlet线程安全问题
Servlet采用单实例多线程方式运行,因此是线程不安全的.默认情况下,非分布式系统,Servlet容器只会维护一个Servlet的实例,当多个请求到达同一个Servlet时,Servlet容器会启动 ...
- (2.1)servlet线程安全问题
本文参考链接:http://www.yesky.com/334/1951334.shtml 摘 要:介绍了Servlet多线程机制,通过一个实例并结合Java 的内存模型说明引起Servlet线程不安 ...
- 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映射匹配问题: 在第一篇文章中的 ...
- (转)JavaWeb学习之Servlet(三)----Servlet的映射匹配问题、线程安全问题
[声明] 欢迎转载,但请保留文章原始出处→_→ 文章来源:http://www.cnblogs.com/smyhvae/p/4140529.html 一.Servlet映射匹配问题: 在第一篇文章中的 ...
随机推荐
- java基础(四)之this的使用
作用: 1.使用this调用成员变量和成员函数2.使用this调用构造函数 Person.java: class Person{ String name; //成员变量 void talk(Strin ...
- Unity 坐标系转换
在Unity中我们通常会用到以下几个坐标系下的点: 世界坐标系:World Space 简单来讲,我们通过 transform.position | transform.rotation 获取得到的位 ...
- 【转】继承了母版页的子页面中触发body的onload事件
碰到有个继承了母版页的页面要加载body的onload事件,我发了一下午来解决这个问题,终于在国外某论坛上找到了解决方案 Method1: In the master page make the bo ...
- 2019-08-01 纪中NOIP模拟B组
T1 [JZOJ2642] 游戏 题目描述 Alice和Bob在玩一个游戏,游戏是在一个N*N的矩阵上进行的,每个格子上都有一个正整数.当轮到Alice/Bob时,他/她可以选择最后一列或最后一行,并 ...
- 配置本地https
参考 https://juejin.im/post/5a6db896518825732d7fd8e0 https://juejin.im/post/590ec765a22b9d0058fcfaa5 比 ...
- 解决修改JDK环境变量不生效方法
解决修改JDK环境变量不生效方法 brupsuit1.7在安装时一直报错jdk版本低,我就将jdk1.6版本的卸了换成1.8的,结果修改了环境变量但它一直给我不生效.... 1.之前版本未卸载干净 进 ...
- Java-POJ1014-Dividing
多重背包问题的特点是物品数量可以大于1但是有限制.状态定义与01背包一致. 多重背包的解法有多种,复杂度也各不相同. 对于物品数Ci较大的数据,可以采取二进制数进行优化(就是这样,别问就是baidu! ...
- 状态压缩DP入门题
. /*本题为状态压缩题 题目大意 : 一个矩阵里有很多格子,每个格子有两种状态,可以放牧和不可以放牧, 可以放牧用1表示,否则用0表示,在这块牧场放牛,要求两个相邻的方 格不能同时放牛(不包括斜着的 ...
- Atcoder Beginner Contest 155D(二分,尺取法,细节模拟)
二分,尺取法,细节模拟,尤其是要注意a[i]被计算到和a[i]成对的a[j]里时 #define HAVE_STRUCT_TIMESPEC #include<bits/stdc++.h> ...
- B. Game with string 思维问题转化
B. Game with string 思维问题转化 题意 有一个字符串 每次可以删去连续的两个同样的字符,两个人轮流删,问最后谁能赢 思路 初看有点蒙蔽,仔细看看样例就会发现其实就是一个括号匹配问题 ...