`

j2ee的线程安全--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:

Java代码 复制代码
  1. public class SimpleServlet extends HttpServlet   
  2. {   
  3.     // A variable that is NOT thread-safe!   
  4.     private int counter = 0;   
  5.   
  6.     public void doGet(HttpServletRequest req, HttpServletResponse resp)   
  7.             throws ServletException, IOException   
  8.     {   
  9.         doPost(req, resp);   
  10.     }   
  11.   
  12.     public void doPost(HttpServletRequest req, HttpServletResponse resp)   
  13.             throws ServletException, IOException   
  14.     {   
  15.         resp.getWriter().println("<HTML><BODY>");   
  16.         resp.getWriter().println(this + " ==> ");   
  17.         resp.getWriter().println(Thread.currentThread() + ":   
  18. ");   
  19.            
  20.         for (int c = 0; c < 10; c++)   
  21.         {   
  22.             resp.getWriter().println("Counter = " + counter + "  
  23. ");   
  24.             try  
  25.             {   
  26.                 Thread.sleep((long) Math.random() * 1000);   
  27.                 counter++;   
  28.             }   
  29.             catch (InterruptedException exc)   
  30.             {   
  31.             }   
  32.         }   
  33.         resp.getWriter().println("</BODY></HTML>");   
  34.     }   
  35. }  
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代码 复制代码
  1. <HTML>  
  2. <BODY>  
  3. <TABLE>  
  4. <TR>  
  5. <TD><IFRAME src="./SimpleServlet" name="servlet1" height="200%"> </IFRAME></TD>  
  6. </TR>  
  7. <TR>  
  8.   
  9. <TD><IFRAME src="./SimpleServlet" name="servlet2" height="200%"> </IFRAME></TD>  
  10.   
  11. </TR>  
  12. <TR>  
  13.   
  14. <TD><IFRAME src="./SimpleServlet" name="servlet3" height="200%"> </IFRAME></TD>  
  15.   
  16. </TR>  
  17.   
  18. </TABLE>  
  19. </BODY>  
  20. </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>

    

刷新页面几次后,产生的结果为:

Java代码 复制代码
  1. com.zwchen.servlet.SimpleServlet@11e1bbf ==> Thread[http-8081-Processor23,5,main]:    
  2. Counter = 60  
  3. Counter = 61  
  4. Counter = 62  
  5. Counter = 65  
  6. Counter = 68  
  7. Counter = 71  
  8. Counter = 74  
  9. Counter = 77  
  10. Counter = 80  
  11. Counter = 83  
  12.   
  13.   
  14.   
  15. com.zwchen.servlet.SimpleServlet@11e1bbf ==> Thread[http-8081-Processor22,5,main]:    
  16. Counter = 61  
  17. Counter = 63  
  18. Counter = 66  
  19. Counter = 69  
  20. Counter = 72  
  21. Counter = 75  
  22. Counter = 78  
  23. Counter = 81  
  24. Counter = 84  
  25. Counter = 87  
  26.   
  27.   
  28.   
  29. com.zwchen.servlet.SimpleServlet@11e1bbf ==> Thread[http-8081-Processor24,5,main]:    
  30. Counter = 61  
  31. Counter = 64  
  32. Counter = 67  
  33. Counter = 70  
  34. Counter = 73  
  35. Counter = 76  
  36. Counter = 79  
  37. Counter = 82  
  38. Counter = 85  
  39. 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代码重构如下:

Java代码 复制代码
  1. public class SimpleServlet extends HttpServlet   
  2.   
  3. {   
  4.     // A variable that is NOT thread-safe!   
  5.     private int counter = 0;   
  6.     private String mutex = "";   
  7.        
  8.     public void doGet(HttpServletRequest req, HttpServletResponse resp)   
  9.     throws ServletException, IOException   
  10.     {   
  11.         doPost(req, resp);   
  12.     }   
  13.   
  14.     public void doPost(HttpServletRequest req, HttpServletResponse resp)   
  15.     throws ServletException, IOException   
  16.     {   
  17.         resp.getWriter().println("<HTML><BODY>");   
  18.         resp.getWriter().println(this + ":   
  19. ");   
  20.         synchronized (mutex)   
  21.         {   
  22.             for (int c = 0; c < 10; c++)   
  23.             {   
  24.                 resp.getWriter().println("Counter = " + counter + "  
  25. ");   
  26.                 try  
  27.                 {   
  28.                     Thread.sleep((long) Math.random() * 1000);   
  29.                     counter++;   
  30.                 }   
  31.                 catch (InterruptedException exc) {   
  32.                 }   
  33.             }   
  34.         }   
  35.         resp.getWriter().println("</BODY></HTML>");   
  36.     }   
  37. }  
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>");
	}
}


我们的输出结果为:

Java代码 复制代码
  1. com.zwchen.servlet.SimpleServlet@109da93:    
  2. Counter = 0  
  3. Counter = 1  
  4. Counter = 2  
  5. Counter = 3  
  6. Counter = 4  
  7. Counter = 5  
  8. Counter = 6  
  9. Counter = 7  
  10. Counter = 8  
  11. Counter = 9  
  12.   
  13.   
  14.   
  15. com.zwchen.servlet.SimpleServlet@109da93:    
  16. Counter = 10  
  17. Counter = 11  
  18. Counter = 12  
  19. Counter = 13  
  20. Counter = 14  
  21. Counter = 15  
  22. Counter = 16  
  23. Counter = 17  
  24. Counter = 18  
  25. Counter = 19  
  26.   
  27.   
  28.   
  29. com.zwchen.servlet.SimpleServlet@109da93:    
  30. Counter = 20  
  31. Counter = 21  
  32. Counter = 22  
  33. Counter = 23  
  34. Counter = 24  
  35. Counter = 25  
  36. Counter = 26  
  37. Counter = 27  
  38. Counter = 28  
  39. 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代码重构如下:

Java代码 复制代码
  1. public class SimpleServlet extends HttpServlet   
  2. {   
  3.     private ThreadLocal counter = new ThreadLocal() {   
  4.         protected synchronized Object initialValue()   
  5.         {   
  6.             return new Integer(0);   
  7.         }   
  8.     };   
  9.   
  10.     public void doGet(HttpServletRequest req, HttpServletResponse resp)   
  11.             throws ServletException, IOException   
  12.     {   
  13.         doPost(req, resp);   
  14.     }   
  15.   
  16.     public void doPost(HttpServletRequest req, HttpServletResponse resp)   
  17.             throws ServletException, IOException   
  18.     {   
  19.         resp.getWriter().println("<HTML><BODY>");   
  20.         resp.getWriter().println(   
  21.                 this + "[" + Thread.currentThread() + "]:   
  22. ");   
  23.         for (int c = 0; c < 10; c++)   
  24.         {   
  25.             resp.getWriter().println(counter.get() + "  
  26. ");   
  27.             try  
  28.             {   
  29.                 Thread.sleep((long) Math.random() * 1000);   
  30.                 int c1 = ((Integer) counter.get()).intValue();   
  31.                 c1++;   
  32.                 counter.set(new Integer(c1));   
  33.             }   
  34.             catch (InterruptedException exc)   
  35.             {   
  36.             }   
  37.         }   
  38.   
  39.         resp.getWriter().println("</BODY></HTML>");   
  40.     }   
  41. }  
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页面三次,第三次结果如下:

Java代码 复制代码
  1. com.zwchen.servlet.SimpleServlet@124e935[Thread[http-8081-Processor22,5,main]]:    
  2. 20  
  3. 21  
  4. 22  
  5. 23  
  6. 24  
  7. 25  
  8. 26  
  9. 27  
  10. 28  
  11. 29  
  12.   
  13. com.zwchen.servlet.SimpleServlet@124e935[Thread[http-8081-Processor25,5,main]]:    
  14. 20  
  15. 21  
  16. 22  
  17. 23  
  18. 24  
  19. 25  
  20. 26  
  21. 27  
  22. 28  
  23. 29  
  24.   
  25. com.zwchen.servlet.SimpleServlet@124e935[Thread[http-8081-Processor23,5,main]]:    
  26. 20  
  27. 21  
  28. 22  
  29. 23  
  30. 24  
  31. 25  
  32. 26  
  33. 27  
  34. 28  
  35. 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类:

Java代码 复制代码
  1. public class ActionContext implements Serializable {   
  2.   
  3. static ThreadLocal actionContext = new ActionContextThreadLocal();   
  4.   
  5. ……………  
public class ActionContext implements Serializable {

static ThreadLocal actionContext = new ActionContextThreadLocal();

……………




而对于Struts,我们可以从ActionServlet.process() => RequestProcessor. processActionPerform,在RequestProcessor中有字段   protected HashMap actions = new HashMap();我们不难发现,我们所写的action是共享的,那么内部字段必然也是共享。注意,这种共享类似于Servlet里面的字段。

分享到:
评论
1 楼 zjhzwx1212 2014-08-20  
为什么用threadLocal后,输出值是从20开始的,而定义的时候是new Integer(0)?

相关推荐

    J2EE武功秘籍

    - **ThreadLocal**:提供线程本地变量,每个线程都有自己的副本,避免了多线程环境下的线程安全问题。 **14. override(覆盖), overload(重载) 和 overwrite(重写)** - **Override**:子类覆盖父类的方法。 - **...

    j2ee面试宝典及面试总结题

    - 线程的创建与管理,了解synchronized、volatile、ThreadLocal等关键字。 - ExecutorService和Future接口,以及并发工具类如Semaphore、CyclicBarrier。 8. **容器和服务器** - Tomcat、Jetty、WebLogic和...

    j2ee的aop方式记录日志

    在多线程环境中,我们可以利用ThreadLocal存储用户信息和请求信息,确保这些信息在同一个请求范围内是安全的。 - 在Filter或者Servlet初始化时,将用户和请求信息存入ThreadLocal。 - 在日志切面中,可以直接从...

    j2ee.zip_cxf j2ee_java并发

    一种常见的策略是使用线程局部变量(ThreadLocal)存储每个线程的私有数据,避免共享状态的冲突。此外,通过使用Future和Callable接口,我们可以异步执行耗时的操作,如数据库查询,然后在主线程中等待结果,这有助...

    J2EE高性能编程

    例如,可以使用线程局部变量(ThreadLocal)来缓存和复用对象。 - **将同步降至最低** 同步操作是多线程环境中的一大性能瓶颈。尽可能减少同步操作的数量,例如通过使用不可变对象或采用无锁编程技术。 - **尽可能...

    J2EE开发全程实录PDF J2EE开发全程实录PDF

    ### J2EE开发全程实录知识点总结 #### 正则表达式 - **为何使用正则表达式**:正则表达式是一种强大的文本处理工具,能够帮助开发者在字符串操作上节省大量的时间和精力。 - **正则表达式入门**: - **元字符用法...

    轻量级J2EE企业应用实战(part 4)共分5个part

    - **ThreadLocal**: 用来实现线程间的数据隔离,确保每个线程都有自己的副本,避免了多线程间的同步问题。 ##### 3. Session的创建与管理 - **不加拦截器的Session创建**:通过`sessionFactory.openSession()`直接...

    笔试面试题国内知名公司

    - 线程的创建与同步,如synchronized、volatile关键字 - Lock接口和ReentrantLock的使用 - ThreadLocal原理及应用场景 - 并发工具类:CountDownLatch、CyclicBarrier、Semaphore等 5. **Java IO/NIO** - 字节...

    java面试题

    这些题目涵盖了Java的基础知识,类与包的使用,线程安全问题,数据库交互,服务器理解以及J2EE相关技术。以下是对这些关键领域的详细解读: 1. **Java基础**: - 类与对象:Java是一种面向对象的编程语言,类是...

    通向架构师的道路

    在实际项目中,正确使用ThreadLocal可以有效地解决线程安全问题。 这本书对于那些希望提升到架构级别技术的Java开发者来说,提供了非常全面的指南。涵盖了从基础架构到高级技术应用的广泛主题,同时也强调了实战...

    Hibernate中文手册

    - 使用ThreadLocal来维护线程间的会话隔离。 **3.配置** - **可编程的配置方式**: - 通过代码配置SessionFactory。 - **获得SessionFactory**: - 创建SessionFactory实例的过程。 - **JDBC连接**: - 配置...

    java方向程序员面试资料集合,保证对你的面试有利,非常全面,涵盖j2ee,J2se,j2me,内部资料,拼命传出来的,忘各位珍惜

    主要包括Java语言基础、面向对象编程、集合框架、异常处理、多线程、输入/输出流、网络编程等内容。其中,集合框架如ArrayList、LinkedList、HashMap等是面试常考知识点,理解其原理和应用场景至关重要。 2. **J2EE...

    很全的java面试资料

    - **多线程**:线程的创建方式,同步机制(synchronized,wait(),notify(),ThreadLocal),线程池(ExecutorService)。 2. **数据库知识**: - **SQL基础**:掌握SELECT,INSERT,UPDATE,DELETE语句,学会...

    2019年Java面试题集格式.pdf

    - **线程同步**:volatile关键字的作用及其实现原理,ThreadLocal的使用场景。 - **编程**:具体编程实践,如设计模式的应用、代码优化技巧等。 - **设计模式**:单例模式、工厂模式、策略模式等常见设计模式的...

    Acegi使用.pdf

    2. **ContextHolder对象**:利用ThreadLocal存储Authentication对象,确保每个线程的独立性和安全性。 3. **AuthenticationManager**:负责验证由ContextHolder持有的Authentication对象,是整个认证过程的核心。 ...

    J2EE开发全程实录(JAVA项目开发)

    这部分讲述了多账套的实现,线程变量管理(使用ThreadLocal),事务管理,如事务的边界、隔离级别和Spring的声明型事务。还讨论了会话服务的生命周期管理以及IValueObject接口,为数据访问提供基础支持。 【层间...

    2008JAVA工程师高级培训教程6

    2. **线程**:多线程是Java的一个强项,这部分可能会深入讲解线程的创建、同步、协作以及死锁的避免。包括synchronized关键字、wait()和notify()方法、ThreadLocal、线程池(ExecutorService)等概念。 3. **图形...

Global site tag (gtag.js) - Google Analytics