前言:前面说了很多关于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:变量为啥会存在线程安全

我们先看一段代码

 public class HelloWorldServlet extends HttpServlet{
private String userName;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException
{
userName=request.getParameter("userName");
PrintWriter out=response.getWriter();
if(userName!=null&&userName!="")
{
out.print(userName);
}
else {
out.println("用户名不存在");
}
}
}

我们来分析这段代码,现在有A,B2个客户端同时请求HelloWorldServlet 这个实例,Servlet容器分配线程T1来服务A客户端的请求,T2来服务B客户端的请求,操作系统首先调用T1来运行,T1运行到第6行的时候得到了用户名为张三并保存,此时时间片段到了,操作系统开始调用T2运行也运行到第6行但是这个用户名是李四,此时时间片段又到了,操作系统又开始运行T1,从第7行开始运行,但是此时的用户名却成了李四,输出的时候确实李四(很明显是错误的),那么这个时候就出现了线程安全问题。

2.1.2:如何防止变量的线程安全

  1. 把全局变量改为局部变量
    1. protected  void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException
      {
      String userName=request.getParameter("userName");
      PrintWriter out=response.getWriter();
      if(userName!=null&&userName!="")
      {
      out.print(userName);
      }
      else {
      out.println("用户名不存在");
      }
      }

      因为每次调用这个方法的时候就会重写对userName实例化这样一来就不会存在线程安全问题了。

  2. 使用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的属性进行设置和访问,所以这个时候就会出现线程安全问题。我们来看一段代码

 protected void service(HttpServletRequest request, HttpServletResponse response)
{
String userName=request.getParameter("userName");
if ("login") {
List list=(List)getServletContext().getAttribute("userList");
list.add(userName);
}
else {
List list=(List)getServletContext().getAttribute("userList");
list.remove(userName);
}
}
 protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException
{
List list=(List)getServletContext().getAttribute("userList");
int count=list.size();
for(int i=0;i<count;i++)
{
PrintWriter out=response.getWriter();
out.println(list.get(i));
}
}

第一段代码是当用户登录以后把用户名保存在ServletContext属性中,如果不是登录就删除这个用户

第二段代码就是查看应用程序所有的用户登录情况,那么我们看如何出现线程安全问题的

当2个请求并发执行的时候,可能第二段代码刚刚执行第五行的时候获取的count=5;但是呢另一个请求恰好执行第一段代码第十行,把其中的某个用户删除了,当第二段代码在循环遍历的时候运行到count=5的时候就会数组超过索性界限异常。那么此时就出现了线程安全问题。那么遇到这样的问题怎么解决呢,第一就是把ServletContext属性值进行拷贝保存起来,第二就是采用synchronized 进行同步(这个效率低)

2.2.2:HttpSession

httpSession对象在用户会话期间存活的,不像ServletContext一样被所有的用户共享,所以说一个HttpSession在同一个时刻只用一个用户进行请求的,因此理论看来Session是线程安全的,其实并不是如此,这个和浏览器有关,在上一篇Session我们说过,同一个浏览器只能具有一个Session,那么这样一来就会出现Session线程安全问题,看如下代码

     protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException
{
String commandType=request.getParameter("commandType");
HttpSession session=request.getSession();
List list=(List)session.getAttribute("items");
if ("add".equals(commandType)) {
//添加
}
else if("delete".equals(commandType)){
//删除
}
else {
int count=list.size();
for (int i = 0; i < count; i++) {
//遍历
}
}
}

上面是一个添加物品信息的一个简单伪代码,如果用户现在在一个浏览器窗口删除一件物品的同时又在另一个窗口去获取所有的物品这个时候就会出现线程安全,从上面的介绍得知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)即可),避免影响性能

javaweb回顾第六篇谈一谈Servlet线程安全问题的更多相关文章

  1. javaweb回顾第七篇jsp

    1:为什么会有JSP jsp全名(java server pages)中文叫做java服务器页面.在Servlet那一篇我们发现用Servlet可以生成动态页面,但是我们却在Servlet中却写了大量 ...

  2. javaweb回顾第五篇浅谈会话

    1:什么是会话 通俗来说就是客户和服务器的一次私密谈话,客户发送请求以后服务器能够识别请求是来自同一个客户,他们是1对1的关系. 了解会话以后我们就要去考虑如何去实现这些问题下面一一进行解析 2:会话 ...

  3. javaweb回顾第十篇JSTL

    前言:JSTL(JSP Standard Tag Library)JSP标准标签库.它的目的是为了简化JSP的开发,如何没有JSTL可能我们开发的时候就需要写大量的自定义标签,无疑会加大开发难度,有了 ...

  4. javaweb回顾第八篇如何创建自定义标签

    前言:在javaweb开发中自定义标签的用处还是挺多的.今天和大家一起看自定义标签是如何实现的. 1:什么是标签 标签是一种XML元素,通过标签可以使JSP页面变得简介易用,而且标签具有很好的复用性. ...

  5. javaweb回顾第四篇Servlet异常处理

    前言:很多网站为了给用户很好的用户体验性,都会提供比较友好的异常界面,现在我们在来回顾一下Servlet中如何进行异常处理的. 1:声明式异常处理 什么是声明式:就是在web.xml中声明对各种异常的 ...

  6. javaweb回顾第三篇数据库访问

    前言:本篇主要针对数据库的操作,在这里不适用hibernate或者mybatis,用最原始的JDBC进行讲解,通过了解这些原理以后更容易理解和学习hibernate或mybatis. 1:jdbc的简 ...

  7. javaWeb核心技术第六篇之BootStrap

    概述: Bootstrap 是最受欢迎的 HTML.CSS 和 JS 框架,用于开发响应式布局.移动设备优先的 WEB 项目. 作用: 开发响应式的页面 响应式:就是一个网站能够兼容多个终端 节约开发 ...

  8. javaweb回顾第十一篇过滤器(附实现中文乱码问题)

    1:过滤器概念 过滤器就是一种在请求目标资源的中间组件,比喻把污水转换成纯净水中间需要一个污水净化设备,那么这个设备就好比一个过滤器.那么我用图来表示过滤器(可以有多个过滤器)运行的过程 2:Filt ...

  9. Python之路(第四十六篇)多种方法实现python线程池(threadpool模块\multiprocessing.dummy模块\concurrent.futures模块)

    一.线程池 很久(python2.6)之前python没有官方的线程池模块,只有第三方的threadpool模块, 之后再python2.6加入了multiprocessing.dummy 作为可以使 ...

随机推荐

  1. Linux命令(1)- grep

    1.grep 功能:查找文件里符合条件的字符串. 语法:grep [-abcEFGhHilLnqrsvVwxy][-A<显示列数>][-B<显示列数>][-C<显示列数& ...

  2. 简易版CMS后台管理系统开发流程

    目录 简易版CMS后台管理系统开发流程 MVC5+EF6 简易版CMS(非接口) 第一章:新建项目 MVC5+EF6 简易版CMS(非接口) 第二章:建数据模型 MVC5+EF6 简易版CMS(非接口 ...

  3. fedora 安装vmwear

    Fedora 13下安装后缀为bundle文件,网上的说法很多,最普遍的方法是: 你的登陆名为TEST那么就将要安装的文件放在TEST目录下,不要放到目录下的子目录上面,否则不能运行.然后执行 第一步 ...

  4. c#输出、输入练习

    //输出 Console.WriteLine("这是一行文字");  自动回车的. Console.Write("Hello world");  不带回车的. ...

  5. python学习之路-day7

    本节内容: 面向对象高级语法部分 静态方法.类方法.属性方法 类的特殊方法 反射 异常处理 Socket开发基础 面向对象高级语法部分 静态方法                             ...

  6. UVAlive3486_Cells

    给一棵树,每次每次询问一个点是否是另一个点的祖先? 首先,题目的读入就有点坑爹,注意,每个节点的值是说明它下面有多少个儿子节点,直接对于每个下标保存一个值即可. 对于查询是否是祖先,我们可以对于每一个 ...

  7. discuz数据库表

    http://faq.comsenz.com/library/database/x3/x3_index.htm    discuz数据库表字典 Discuz X3各数据库表用途 pre_common_ ...

  8. java设计模式之外观模式(门面模式)

    针对外观模式,在项目开发和实际运用中十分频繁,但是其极易理解,下面就简要介绍一下. 一.概念介绍 外观模式(Facade),他隐藏了系统的复杂性,并向客户端提供了一个可以访问系统的接口.这种类型的设计 ...

  9. 传递引用类型参数(ref)

    引用类型的变量不直接包含其数据:它包含的是对其数据的引用. 当通过值传递引用类型的参数时,有可能更改引用所指向的数据,如某类成员的值. 但是无法更改引用本身的值:也就是说,不能使用相同的引用为新类分配 ...

  10. Ubuntu远程开机 (Wake on Lan)

    启动者(A) 被远程开启者(B) 一.被远程开启的电脑(电脑B):1. 重新开机,并进到BIOS设定2. 把Wake On Land / Wake On PCI(E)设为Enable3. 储存并进入U ...