- 浏览: 1183150 次
- 性别:
- 来自: 上海
文章分类
- 全部博客 (361)
- java综合 (33)
- 项目管理 (10)
- 工作流 (6)
- spring (11)
- hibenate (17)
- struts (0)
- javascript,html,css,ajax,jquery (11)
- IDE (9)
- 生活 (0)
- 工作 (0)
- 杂记 (1)
- 数据库 (96)
- 服务器 (5)
- 可视编辑 (0)
- freemarker (6)
- 操作系统 windows (13)
- web页面 (6)
- jms (15)
- 调优 (4)
- 测试和bug管理 (2)
- 原理 (1)
- 項目-atf (17)
- 安全 (3)
- xml (4)
- 操作系统 liunx (21)
- 网络 (22)
- office (11)
- 设计 (2)
- 软件 (1)
- 数据库 mysql (6)
- 胖客户端-flex (1)
- 正则 (9)
- oracle- liunx (3)
- sql2000 (2)
- 模式 (1)
- 虚拟机 (2)
- jstl (2)
- 版本控制 打包工具 (0)
- AOP (1)
- demo (1)
- 小软件 (2)
- 感恩 (1)
- iphone 4 (1)
- 反欺诈业务经验整理 (0)
最新评论
-
sea0108:
mark
java内存模型 -
XingShiYiShi:
方便把:testPNR();具体实现发出来吗?谢谢
用正则表达式解析 航信的电子客票和pnr报文 -
wh359126613:
如果js和webservice不在同一个服务器上,有跨域问题如 ...
使用javascript调用webservice示例 -
雨飛雁舞:
...
oracle 动态性能(V$)视图 -
ouyang1224:
好东西
oracle 动态性能(V$)视图
关键字: j2ee servlet 线程 安全 threadlocal
1.在用Servlet时,我们都知道Servlet只会被初始化一次,只有一个实例。
2.在Struts1中,ActionServlet也仅是初始化一次,也是单实例。
为什么会这样,无非是为了提高效率。但是线程安全不容忽视。但是在WebWork、Struts2却做好了线程安全。
下面具体介绍:
Servlet线程安全
概述
在探讨java线程安全前,让我们先简要介绍一下Java语言。
任何语言,如C++,C#,Java,它们都有相通之处,特别是语法,但如果有人问你,Java语言的核心是什么?类库?关键字?语法?似乎都不是。Java语言的核心,也就是Sun始终不愿意开源的东西:Java虚拟机的实现(不过sun公开了其Java虚拟机规范),也就有了BEA的JRockit,IBM的Jikes,Sun的Hotspot。
Java的核心有两点,Java类加载(Java Class Loader)和Java内存管理,它们具体体现在Java类库的以下几个类:
java.lang.ClassLoader(java.lang.Class):我们调用的类,包括其接口和超类,import的类是怎么被Java虚拟机载入的?为什么static的字段在servlet容器里面可以一直生存下去(Spring容器中)?
java.lang.Thread(java.lang.ThreadLocal):垃圾回收是怎么进行的(垃圾回收线程)?我们的程序是怎么退出的?
java.lang.refelect.Proxy(java.lang.refelect.Method):为什么Tomcat、Tapestry、Webwork、Spring等容器和框架可以通过配置文件来调用我们写的类?Servlet规范、JSF规范、EJB规范、JDBC规范究竟是怎么回事?为什么它们几乎都是一些接口,而不是具体类?
Servlet线程安全
在Java的server side开发过程中,线程安全(Thread Safe)是一个尤为突出的问题。因为容器,如Servlet、EJB等一般都是多线程运行的。虽然在开发过程中,我们一般不考虑这些问题,但诊断问题(Robust),程序优化(Performance),我们必须深入它们。
什么是线程安全?
Thread-safe describes a program portion or routine that can be called from multiple programming threads without unwanted interaction between the threads。
在Java里,线程安全一般体现在两个方面:
1. 多个thread对同一个java实例的访问(read和modify)不会相互干扰,它主要体现在关键字synchronized。如ArrayList和Vector,HashMap和Hashtable(后者每个方法前都有synchronized关键字)。如果你在interator一个List对象时,其它线程remove一个element,问题就出现了。
2. 每个线程都有自己的字段,而不会在多个线程之间共享。它主要体现在java.lang.ThreadLocal类,而没有Java关键字支持,如像static、transient那样。
一个普遍的疑问,我们的Servlet中能够像JavaBean那样declare instance或static字段吗?如果不可以?会引发什么问题?
答案是:不可以。我们下面以实例讲解:
首先,我们写一个普通的Servlet,里面有instance字段count:
- public class SimpleServlet extends HttpServlet
- {
- // A variable that is NOT thread-safe!
- private int counter = 0;
- public void doGet(HttpServletRequest req, HttpServletResponse resp)
- throws ServletException, IOException
- {
- doPost(req, resp);
- }
- public void doPost(HttpServletRequest req, HttpServletResponse resp)
- throws ServletException, IOException
- {
- resp.getWriter().println("<HTML><BODY>");
- resp.getWriter().println(this + " ==> ");
- resp.getWriter().println(Thread.currentThread() + ": <br>");
- for (int c = 0; c < 10; c++)
- {
- resp.getWriter().println("Counter = " + counter + "<BR>");
- try
- {
- Thread.sleep((long) Math.random() * 1000);
- counter++;
- }
- catch (InterruptedException exc)
- {
- }
- }
- resp.getWriter().println("</BODY></HTML>");
- }
- }
public class SimpleServlet extends HttpServlet { // A variable that is NOT thread-safe! private int counter = 0; public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doPost(req, resp); } public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.getWriter().println("<HTML><BODY>"); resp.getWriter().println(this + " ==> "); resp.getWriter().println(Thread.currentThread() + ": <br>"); for (int c = 0; c < 10; c++) { resp.getWriter().println("Counter = " + counter + "<BR>"); try { Thread.sleep((long) Math.random() * 1000); counter++; } catch (InterruptedException exc) { } } resp.getWriter().println("</BODY></HTML>"); } }
然后,我们通过一个html页面向该servlet发出三次请求:
- <HTML>
- <BODY>
- <TABLE>
- <TR>
- <TD><IFRAME src="./SimpleServlet" name="servlet1" height="200%"> </IFRAME></TD>
- </TR>
- <TR>
- <TD><IFRAME src="./SimpleServlet" name="servlet2" height="200%"> </IFRAME></TD>
- </TR>
- <TR>
- <TD><IFRAME src="./SimpleServlet" name="servlet3" height="200%"> </IFRAME></TD>
- </TR>
- </TABLE>
- </BODY>
- </HTML>
<HTML> <BODY> <TABLE> <TR> <TD><IFRAME src="./SimpleServlet" name="servlet1" height="200%"> </IFRAME></TD> </TR> <TR> <TD><IFRAME src="./SimpleServlet" name="servlet2" height="200%"> </IFRAME></TD> </TR> <TR> <TD><IFRAME src="./SimpleServlet" name="servlet3" height="200%"> </IFRAME></TD> </TR> </TABLE> </BODY> </HTML>
刷新页面几次后,产生的结果为:
- com.zwchen.servlet.SimpleServlet@11e1bbf ==> Thread[http-8081-Processor23,5,main]:
- Counter = 60
- Counter = 61
- Counter = 62
- Counter = 65
- Counter = 68
- Counter = 71
- Counter = 74
- Counter = 77
- Counter = 80
- Counter = 83
- com.zwchen.servlet.SimpleServlet@11e1bbf ==> Thread[http-8081-Processor22,5,main]:
- Counter = 61
- Counter = 63
- Counter = 66
- Counter = 69
- Counter = 72
- Counter = 75
- Counter = 78
- Counter = 81
- Counter = 84
- Counter = 87
- com.zwchen.servlet.SimpleServlet@11e1bbf ==> Thread[http-8081-Processor24,5,main]:
- Counter = 61
- Counter = 64
- Counter = 67
- Counter = 70
- Counter = 73
- Counter = 76
- Counter = 79
- Counter = 82
- Counter = 85
- Counter = 88
com.zwchen.servlet.SimpleServlet@11e1bbf ==> Thread[http-8081-Processor23,5,main]: Counter = 60 Counter = 61 Counter = 62 Counter = 65 Counter = 68 Counter = 71 Counter = 74 Counter = 77 Counter = 80 Counter = 83 com.zwchen.servlet.SimpleServlet@11e1bbf ==> Thread[http-8081-Processor22,5,main]: Counter = 61 Counter = 63 Counter = 66 Counter = 69 Counter = 72 Counter = 75 Counter = 78 Counter = 81 Counter = 84 Counter = 87 com.zwchen.servlet.SimpleServlet@11e1bbf ==> Thread[http-8081-Processor24,5,main]: Counter = 61 Counter = 64 Counter = 67 Counter = 70 Counter = 73 Counter = 76 Counter = 79 Counter = 82 Counter = 85 Counter = 88
我们会发现三点:
servlet只产生了一个Servlet对象,因为输出this时,其hashcode都一样,
servlet在不同的线程(线程池)中运行,如http-8081-Processor22,http-8081-Processor23
Count被这三个doGet方法共享,并且并行修改。
上面的结果,违反了线程安全的两个方面。
那么,我们怎样保证按照我们期望的结果运行呢?首先,我想保证产生的count都是顺序执行的。
我们将Servlet代码重构如下:
- public class SimpleServlet extends HttpServlet
- {
- // A variable that is NOT thread-safe!
- private int counter = 0;
- private String mutex = "";
- public void doGet(HttpServletRequest req, HttpServletResponse resp)
- throws ServletException, IOException
- {
- doPost(req, resp);
- }
- public void doPost(HttpServletRequest req, HttpServletResponse resp)
- throws ServletException, IOException
- {
- resp.getWriter().println("<HTML><BODY>");
- resp.getWriter().println(this + ": <br>");
- synchronized (mutex)
- {
- for (int c = 0; c < 10; c++)
- {
- resp.getWriter().println("Counter = " + counter + "<BR>");
- try
- {
- Thread.sleep((long) Math.random() * 1000);
- counter++;
- }
- catch (InterruptedException exc) {
- }
- }
- }
- resp.getWriter().println("</BODY></HTML>");
- }
- }
public class SimpleServlet extends HttpServlet { // A variable that is NOT thread-safe! private int counter = 0; private String mutex = ""; public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doPost(req, resp); } public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.getWriter().println("<HTML><BODY>"); resp.getWriter().println(this + ": <br>"); synchronized (mutex) { for (int c = 0; c < 10; c++) { resp.getWriter().println("Counter = " + counter + "<BR>"); try { Thread.sleep((long) Math.random() * 1000); counter++; } catch (InterruptedException exc) { } } } resp.getWriter().println("</BODY></HTML>"); } }
我们的输出结果为:
- com.zwchen.servlet.SimpleServlet@109da93:
- Counter = 0
- Counter = 1
- Counter = 2
- Counter = 3
- Counter = 4
- Counter = 5
- Counter = 6
- Counter = 7
- Counter = 8
- Counter = 9
- com.zwchen.servlet.SimpleServlet@109da93:
- Counter = 10
- Counter = 11
- Counter = 12
- Counter = 13
- Counter = 14
- Counter = 15
- Counter = 16
- Counter = 17
- Counter = 18
- Counter = 19
- com.zwchen.servlet.SimpleServlet@109da93:
- Counter = 20
- Counter = 21
- Counter = 22
- Counter = 23
- Counter = 24
- Counter = 25
- Counter = 26
- Counter = 27
- Counter = 28
- Counter = 29
com.zwchen.servlet.SimpleServlet@109da93: Counter = 0 Counter = 1 Counter = 2 Counter = 3 Counter = 4 Counter = 5 Counter = 6 Counter = 7 Counter = 8 Counter = 9 com.zwchen.servlet.SimpleServlet@109da93: Counter = 10 Counter = 11 Counter = 12 Counter = 13 Counter = 14 Counter = 15 Counter = 16 Counter = 17 Counter = 18 Counter = 19 com.zwchen.servlet.SimpleServlet@109da93: Counter = 20 Counter = 21 Counter = 22 Counter = 23 Counter = 24 Counter = 25 Counter = 26 Counter = 27 Counter = 28 Counter = 29
这符合了我们的要求,输出都是按顺序的,这正式synchronized的含义。
附带说一下,我现在synchronized的是一个字符串变量mutex,不是this对象,这主要是从performance和Scalability考虑。Synchronized用在this对象上,会带来严重的可伸缩性的问题(Scalability),所有的并发请求都要排队!
现在,我们保证了顺序,但是我们怎么保证Counter字段(不是局部变量!)在每个Servlet的线程下都是独立的呢?也就是说,并发请求时,它们都不相互干扰。
我现在将Servlet代码重构如下:
- public class SimpleServlet extends HttpServlet
- {
- private ThreadLocal counter = new ThreadLocal() {
- protected synchronized Object initialValue()
- {
- return new Integer(0);
- }
- };
- public void doGet(HttpServletRequest req, HttpServletResponse resp)
- throws ServletException, IOException
- {
- doPost(req, resp);
- }
- public void doPost(HttpServletRequest req, HttpServletResponse resp)
- throws ServletException, IOException
- {
- resp.getWriter().println("<HTML><BODY>");
- resp.getWriter().println(
- this + "[" + Thread.currentThread() + "]: <br>");
- for (int c = 0; c < 10; c++)
- {
- resp.getWriter().println(counter.get() + "<br>");
- try
- {
- Thread.sleep((long) Math.random() * 1000);
- int c1 = ((Integer) counter.get()).intValue();
- c1++;
- counter.set(new Integer(c1));
- }
- catch (InterruptedException exc)
- {
- }
- }
- resp.getWriter().println("</BODY></HTML>");
- }
- }
public class SimpleServlet extends HttpServlet { private ThreadLocal counter = new ThreadLocal() { protected synchronized Object initialValue() { return new Integer(0); } }; public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doPost(req, resp); } public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.getWriter().println("<HTML><BODY>"); resp.getWriter().println( this + "[" + Thread.currentThread() + "]: <br>"); for (int c = 0; c < 10; c++) { resp.getWriter().println(counter.get() + "<br>"); try { Thread.sleep((long) Math.random() * 1000); int c1 = ((Integer) counter.get()).intValue(); c1++; counter.set(new Integer(c1)); } catch (InterruptedException exc) { } } resp.getWriter().println("</BODY></HTML>"); } }
现在,我刷新html页面三次,第三次结果如下:
- com.zwchen.servlet.SimpleServlet@124e935[Thread[http-8081-Processor22,5,main]]:
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- com.zwchen.servlet.SimpleServlet@124e935[Thread[http-8081-Processor25,5,main]]:
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- com.zwchen.servlet.SimpleServlet@124e935[Thread[http-8081-Processor23,5,main]]:
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
com.zwchen.servlet.SimpleServlet@124e935[Thread[http-8081-Processor22,5,main]]: 20 21 22 23 24 25 26 27 28 29 com.zwchen.servlet.SimpleServlet@124e935[Thread[http-8081-Processor25,5,main]]: 20 21 22 23 24 25 26 27 28 29 com.zwchen.servlet.SimpleServlet@124e935[Thread[http-8081-Processor23,5,main]]: 20 21 22 23 24 25 26 27 28 29
从以上结果,我们可以发现:
1、在该html页面内的并发三次请求中,该Servlet里面的counter字段都不相互干扰
2、counter字段还是实例字段,并且都会保留状态,不是每次都用0开始
3、html页面内的三次请求都在不同的线程,但在同一个实例中。
总之,在Java里面,字段(不是局部变量)有三个共享范围:instance field,static field,local thread field,而后者往往在服务器端这种多线程环境必须考虑到的。
在J2EE项目开发过程中,ThreadLocal类有时有非常重要的作用,下面是我碰到的,但可以延伸:
1、在用Hibernate做web开发的持久化时,有个模式叫做Open Session In View,也就是将session保留到页面中,在response结束后,在OpenSessionInViewFilter中关闭session,这对于延迟加载非常有效,例如,我们在页面上显示User的详细信息,需要显示该user的所属Department的信息; 但是,在list users这种不需要显示department信息的地方,那个user的department信息就不会加载,也就是说加载相关信息是动态的,但不会出现LazyInitializationException,也就是Load on demand。不过,注意慎用该模式。
2、在工作流开发,例如OSWorflow,每次调用其服务前,都需要将caller对象传入,这样会导致我们的方法非常臃肿,如果我们在调用该方法的上层,如在Servlet里调用它之前,将User对象置于ThreadLocal中,那么可以在工作流方法内通过get()方法获取,而不用传入参数。
3、为什么Web框架中,Webwork的action中可以有field,但Struts却不能?其实,也就是说,Struts不是线程安全的,而Webwork是线程安全的。大家可以参考Webwork的ActionContext类:
- public class ActionContext implements Serializable {
- static ThreadLocal actionContext = new ActionContextThreadLocal();
- ……………
public class ActionContext implements Serializable {
static ThreadLocal actionContext = new ActionContextThreadLocal();
……………
而对于Struts,我们可以从ActionServlet.process() => RequestProcessor. processActionPerform,在RequestProcessor中有字段 protected HashMap actions = new HashMap();我们不难发现,我们所写的action是共享的,那么内部字段必然也是共享。注意,这种共享类似于Servlet里面的字段。
发表评论
-
介绍Java GC种类,并更换 调优GC
2011-03-04 03:42 2119转载自:http://blog.csdn.net/rola ... -
Daemonthread(守护线程)的含义
2011-03-03 20:39 1906http://fruitking.iteye.com/blog ... -
smc_5_1_0小例子
2010-06-25 15:33 0smc_5_1_0小例子 -
Hessian 例子
2010-06-25 15:27 0Hessian 例子 -
java小日历带农历(转)
2010-06-20 15:55 1477http://zengming526.iteye.com/bl ... -
Annotation exmaple
2009-12-14 09:44 1067如何使用注解的小例子 -
Java6 WebService @WebService
2009-12-11 10:39 43161. 新建一个JAVA project.2. 新建一个He ... -
JNI 数据类型转换
2009-11-18 12:01 21177Java代码 #include <jni ... -
JNI 返回结构体参数
2009-11-18 11:58 8842如何使用JNI的一些基本方法和过程在网上多如牛毛,如果你对Jn ... -
Map 四种同步方式的性能比较
2009-11-01 21:03 1922如果需要使 Map 线程安全,大致有这么四种方法: 1、使用 ... -
ConcurrentHashMap之实现细节
2009-11-01 21:00 2801ConcurrentHashMap是Java 5中支持高并发、 ... -
Java反射经典实例
2009-09-28 16:30 1980Java提供了一套机制来动态执行方法和构造方法,以及数组操作等 ... -
java toString() ReflectionToStringBuilder
2009-06-18 11:31 2078@Override public String toStrin ... -
解决JAVA服务器性能问题
2009-04-13 01:51 2825解决JAVA服务器性能问题 ... -
java内存模型
2009-04-12 23:10 5618Weblogic系统管理总结(7.1/8.0)关于JVM内存管 ... -
Java线程总结
2009-04-12 20:32 1137在论坛上面常常看到初 ... -
删除字符串中的空格或将字符串的多个空格换位单个空格
2009-04-10 15:44 2470//将字符串的多个空格换位单个空格 public static ... -
Java 中文于unicode 互转
2009-04-06 03:08 3219关键字: java unicode convert 转换 ... -
Java EE 6体系结构的变革
2009-03-27 17:54 1463又看到 Reza 同学为 Java EE 6 奔走呼告了。如同 ... -
体验Java1.5中面向方面(AOP)编程
2009-03-12 19:42 1117对于一个能够访问源代码的经验丰富的Java开发人员来说,任何程 ...
相关推荐
基础知识 线程安全性 定义 当多个线程访问某个类时,这个类始终能表现出正确的行为,那么就称这个类是线程安全的 无状态对象一定是线程安全的,大多数Servlet都是无状态的 原子性 一组不可分割...
【线程】ThreadLocal的作用 90 【Spring】什么是IOC和DI?DI是如何实现的 91 【Spring】spring中的IOC(控制反转)的原理 92 【Spring】什么是AOP 92 【Spring】Spring事务机制 93 声明式事物 93 编程式事务 94 ...
30. 哪些集合类是线程安全的? 12 31. 迭代器 Iterator 是什么? 12 32. Iterator 怎么使用?有什么特点? 12 33. Iterator 和 ListIterator 有什么区别? 13 三、多线程 13 35. 并行和并发有什么区别? 13 36. 线程...
第1章 Java概述 1 1.1 Java语言的发展简史 2 1.2 Java的竞争对手及各自优势 4 1.2.1 C#简介和优势 4 1.2.2 Ruby简介和优势 4 1.2.3 Python的简介和优势 5 ...学生提问:当我们使用编译C程序时,不仅需要指定存放...
9.2 ThreadLocal基础知识 9.2.1 ThreadLocal是什么 9.2.2 ThreadLocal的接口方法 9.2.3 一个TheadLocal实例 9.2.4 与Thread同步机制的比较 9.2.5 Spring使用ThreadLocal解决线程安全问题 9.3 Spring对事务管理的支持...
9.2 ThreadLocal基础知识 9.2.1 ThreadLocal是什么 9.2.2 ThreadLocal的接口方法 9.2.3 一个TheadLocal实例 9.2.4 与Thread同步机制的比较 9.2.5 Spring使用ThreadLocal解决线程安全问题 9.3 Spring对事务管理的支持...
│ │ 一些其它网站的java基础精华贴.txt │ │ 新建 文本文档.txt │ │ 经验总结.txt │ │ 资料目录.txt │ │ 题目.txt │ │ │ ├─HTML Pages │ │ │ Desktop_.ini │ │ │ Low Level Security in Java....
│ │ 一些其它网站的java基础精华贴.txt │ │ 新建 文本文档.txt │ │ 经验总结.txt │ │ 资料目录.txt │ │ 题目.txt │ │ │ ├─HTML Pages │ │ │ Desktop_.ini │ │ │ Low Level Security in Java....
│ │ 一些其它网站的java基础精华贴.txt │ │ 新建 文本文档.txt │ │ 经验总结.txt │ │ 资料目录.txt │ │ 题目.txt │ │ │ ├─HTML Pages │ │ │ Desktop_.ini │ │ │ Low Level Security in Java....
│ │ 一些其它网站的java基础精华贴.txt │ │ 新建 文本文档.txt │ │ 经验总结.txt │ │ 资料目录.txt │ │ 题目.txt │ │ │ ├─HTML Pages │ │ │ Desktop_.ini │ │ │ Low Level Security in Java....
│ │ 一些其它网站的java基础精华贴.txt │ │ 新建 文本文档.txt │ │ 经验总结.txt │ │ 资料目录.txt │ │ 题目.txt │ │ │ ├─HTML Pages │ │ │ Desktop_.ini │ │ │ Low Level Security in Java....