- 浏览: 853963 次
- 性别:
- 来自: 北京
文章分类
最新评论
-
zjhzwx1212:
为什么用threadLocal后,输出值是从20开始的,而定义 ...
j2ee的线程安全--threadlocal -
aeoluspu:
不错 mysql 测试部分感觉不详细
用sysbench(或者super-smack)测试mysql性能 -
nanPrivate:
有没有例子,只理论,实践起来还是不会啊
JMS可靠消息传送 -
lwclover:
一个网络工程师 装什么b
postfix 如何删除队列中的邮件 -
maimode:
我也欠缺不少啊
理想的计算机科学知识体系
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() + ":
- ");
- for (int c = 0; c < 10; c++)
- {
- resp.getWriter().println("Counter = " + counter + "
- ");
- 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 + ":
- ");
- synchronized (mutex)
- {
- for (int c = 0; c < 10; c++)
- {
- resp.getWriter().println("Counter = " + counter + "
- ");
- 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() + "]:
- ");
- for (int c = 0; c < 10; c++)
- {
- resp.getWriter().println(counter.get() + "
- ");
- 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里面的字段。
发表评论
-
Improving performance and scalability with DDD
2010-09-19 14:57 1082http://gojko.net/2009/06/23/imp ... -
Java EE meets Web 2.0
2010-09-19 14:56 2515http://www.ibm.com/developerwor ... -
最全面的Hibernate 性能优化技巧
2010-08-19 16:28 1194http://www.duka ... -
必备的 Java 参考资源列表
2010-03-16 11:42 1452Java™ 平台不久将迎来 ... -
CWE/SANS发布2010年25个最危险的编程错误
2010-02-26 09:05 1253http://www.infoq.com/cn/news/20 ... -
一个程序员的多年珍藏(Java&Linux)
2010-01-15 17:25 5478http://jythoner.iteye.com/blog/ ... -
Java 推荐读物与源代码阅读
2010-01-15 17:24 1164http://jythoner.iteye.com/blog/ ... -
A Spring Security ACL tutorial for PostgreSQL
2010-01-01 22:48 1547http://server.denksoft.com/word ... -
ddd using jpa
2009-12-28 10:00 1092http://www.iteye.com/topic/6540 ... -
java的ddd框架
2009-12-28 09:43 2728SpringXT是Spring框架的一个扩展用于开发riche ... -
hibernate session cache tips
2009-12-27 22:13 10121、只有当通过主键load或者get时,hibernate才不 ... -
事物tips
2009-12-27 20:27 9801、只读标志只在事务启动时应用。不启动任何事务,则只读标志被忽 ... -
常用正则表达式
2009-12-24 10:11 1025Email : /^\w+([-+.]\w+)*@\w+([- ... -
hibernate batch insert 和id策略
2009-11-17 10:40 1160在id生成策略为native的情况下,batch insert ... -
两个好用的java反编译工具
2009-11-09 09:54 906jad jd-gui -
Design Patterns in Dynamic Programming
2009-11-07 23:04 1078http://norvig.com/design-patter ... -
几个好用的开源工具
2009-11-02 14:20 1180DBDesigner4 一款开源的数据库设计、建模、维 ... -
持续集成的极好例子
2009-10-28 09:56 1981http://www.iteye.com/topic/4993 ... -
fix tomcat memory settings
2009-10-27 16:10 973-Dorg.apache.jasper.runtime.Bod ... -
CI和maven私服
2009-10-22 18:56 1085Nexus quickbuild hudson
相关推荐
- **ThreadLocal**:提供线程本地变量,每个线程都有自己的副本,避免了多线程环境下的线程安全问题。 **14. override(覆盖), overload(重载) 和 overwrite(重写)** - **Override**:子类覆盖父类的方法。 - **...
- 线程的创建与管理,了解synchronized、volatile、ThreadLocal等关键字。 - ExecutorService和Future接口,以及并发工具类如Semaphore、CyclicBarrier。 8. **容器和服务器** - Tomcat、Jetty、WebLogic和...
在多线程环境中,我们可以利用ThreadLocal存储用户信息和请求信息,确保这些信息在同一个请求范围内是安全的。 - 在Filter或者Servlet初始化时,将用户和请求信息存入ThreadLocal。 - 在日志切面中,可以直接从...
一种常见的策略是使用线程局部变量(ThreadLocal)存储每个线程的私有数据,避免共享状态的冲突。此外,通过使用Future和Callable接口,我们可以异步执行耗时的操作,如数据库查询,然后在主线程中等待结果,这有助...
例如,可以使用线程局部变量(ThreadLocal)来缓存和复用对象。 - **将同步降至最低** 同步操作是多线程环境中的一大性能瓶颈。尽可能减少同步操作的数量,例如通过使用不可变对象或采用无锁编程技术。 - **尽可能...
### J2EE开发全程实录知识点总结 #### 正则表达式 - **为何使用正则表达式**:正则表达式是一种强大的文本处理工具,能够帮助开发者在字符串操作上节省大量的时间和精力。 - **正则表达式入门**: - **元字符用法...
- **ThreadLocal**: 用来实现线程间的数据隔离,确保每个线程都有自己的副本,避免了多线程间的同步问题。 ##### 3. Session的创建与管理 - **不加拦截器的Session创建**:通过`sessionFactory.openSession()`直接...
- 线程的创建与同步,如synchronized、volatile关键字 - Lock接口和ReentrantLock的使用 - ThreadLocal原理及应用场景 - 并发工具类:CountDownLatch、CyclicBarrier、Semaphore等 5. **Java IO/NIO** - 字节...
这些题目涵盖了Java的基础知识,类与包的使用,线程安全问题,数据库交互,服务器理解以及J2EE相关技术。以下是对这些关键领域的详细解读: 1. **Java基础**: - 类与对象:Java是一种面向对象的编程语言,类是...
在实际项目中,正确使用ThreadLocal可以有效地解决线程安全问题。 这本书对于那些希望提升到架构级别技术的Java开发者来说,提供了非常全面的指南。涵盖了从基础架构到高级技术应用的广泛主题,同时也强调了实战...
主要包括Java语言基础、面向对象编程、集合框架、异常处理、多线程、输入/输出流、网络编程等内容。其中,集合框架如ArrayList、LinkedList、HashMap等是面试常考知识点,理解其原理和应用场景至关重要。 2. **J2EE...
- **多线程**:线程的创建方式,同步机制(synchronized,wait(),notify(),ThreadLocal),线程池(ExecutorService)。 2. **数据库知识**: - **SQL基础**:掌握SELECT,INSERT,UPDATE,DELETE语句,学会...
- **线程同步**:volatile关键字的作用及其实现原理,ThreadLocal的使用场景。 - **编程**:具体编程实践,如设计模式的应用、代码优化技巧等。 - **设计模式**:单例模式、工厂模式、策略模式等常见设计模式的...
2. **ContextHolder对象**:利用ThreadLocal存储Authentication对象,确保每个线程的独立性和安全性。 3. **AuthenticationManager**:负责验证由ContextHolder持有的Authentication对象,是整个认证过程的核心。 ...
这部分讲述了多账套的实现,线程变量管理(使用ThreadLocal),事务管理,如事务的边界、隔离级别和Spring的声明型事务。还讨论了会话服务的生命周期管理以及IValueObject接口,为数据访问提供基础支持。 【层间...
2. **线程**:多线程是Java的一个强项,这部分可能会深入讲解线程的创建、同步、协作以及死锁的避免。包括synchronized关键字、wait()和notify()方法、ThreadLocal、线程池(ExecutorService)等概念。 3. **图形...