




已阅读5页,还剩6页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
ThreadLocal线程局部变量什么是线程局部变量什么是线程局部变量(thread-local variable)?轻松使用线程: 不共享有时是最好的 ThreadLocal 类是悄悄地出现在 Java 平台版本 1.2 中的。虽然支持线程局部变量早就是许多线程工具(例如 Posix pthreads 工具)的一部分,但 Java Threads API 的最初设计却没有这项有用的功能。而且,最初的实现也相当低效。由于这些原因, ThreadLocal 极少受到关注,但对简化线程安全并发程序的开发来说,它却是很方便的。在 轻松使用线程的第 3 部分,Java 软件顾问 Brian Goetz 研究了 ThreadLocal 并提供了一些使用技巧。 编写线程安全类是困难的。它不但要求仔细分析在什么条件可以对变量进行读写,而且要求仔细分析其它类能如何使用某个类。 有时,要在不影响类的功能、易用性或性能的情况下使类成为线程安全的是很困难的。有些类保留从一个方法调用到下一个方法调用的状态信息,要在实践中使这样 的类成为线程安全的是困难的。管理非线程安全类的使用比试图使类成为线程安全的要更容易些。非线程安全类通常可以安全地在多线程程序中使用,只要您能确保一个线程所用的类的实例不被其它线程使用。例如,JDBC Connection 类是非线程安全的 两个线程不能在小粒度级上安全地共享一个 Connection 但如果每个线程都有它自己的 Connection ,那么多个线程就可以同时安全地进行数据库操作。 不使用 ThreadLocal 为每个线程维护一个单独的 JDBC 连接(或任何其它对象)当然是可能的;Thread API 给了我们把对象和线程联系起来所需的所有工具。而 ThreadLocal 则使我们能更容易地把线程和它的每线程(per-thread)数据成功地联系起来。 什么是线程局部变量(thread-local variable)?线程局部变量高效地为每个使用它的线程提供单独的线程局部变量值的副本。每个线程只能看到与自己相联系的值,而不知道别的线程可 能正在使用或修改它们自己的副本。一些编译器(例如 Microsoft Visual C+ 编译器或 IBM XL FORTRAN 编译器)用存储类别修饰符(像 static 或 volatile )把对线程局部变量的支持集成到了其语言中。Java 编译器对线程局部变量不提供特别的语言支持;相反地,它用 ThreadLocal 类实现这些支持, 核心 Thread 类中有这个类的特别支持。 因为线程局部变量是通过一个类来实现的,而不是作为 Java 语言本身的一部分,所以 Java 语言线程局部变量的使用语法比内建线程局部变量语言的使用语法要笨拙一些。要创建一个线程局部变量,请实例化类 ThreadLocal 的一个对象。 ThreadLocal 类的行为与 java.lang.ref 中的各种 Reference 类的行为很相似; ThreadLocal 类充当存储或检索一个值时的间接句柄。清单 1 显示了 ThreadLocal 接口。 清单 1. ThreadLocal 接口 public class ThreadLocal public Object get(); public void set(Object newValue); public Object initialValue(); get() 访问器检索变量的当前线程的值; set() 访问器修改当前线程的值。 initialValue() 方法是可选的,如果线程未使用过某个变量,那么您可以用这个方法来设置这个变量的初始值;它允许延迟初始化。用一个示例实现来说明 ThreadLocal 的工作方式是最好的方法。清单 2 显示了 ThreadLocal 的一个实现方式。它不是一个特别好的实现(虽然它与最初实现非常相似),所以很可能性能不佳,但它清楚地说明了 ThreadLocal 的工作方式。 清单 2. ThreadLocal 的糟糕实现 public class ThreadLocal private Map values = Collections.synchronizedMap(new HashMap(); public Object get() Thread curThread = Thread.currentThread(); Object o = values.get(curThread); if (o = null & !values.containsKey(curThread) o = initialValue(); values.put(curThread, o); return o; public void set(Object newValue) values.put(Thread.currentThread(), newValue); public Object initialValue() return null; 这个实现的性能不会很好,因为每个 get() 和 set() 操作都需要 values 映射表上的同步,而且如果多个线程同时访问同一个 ThreadLocal ,那么将发生争用。此外,这个实现也是不切实际的,因为用 Thread 对象做 values 映射表中的关键字将导致无法在线程退出后对 Thread 进行垃圾回收,而且也无法对死线程的 ThreadLocal 的特定于线程的值进行垃圾回收。 用 ThreadLocal 实现每线程 Singleton线程局部变量常被用来描绘有状态“单子”(Singleton) 或线程安全的共享对象,或者是通过把不安全的整个变量封装进 ThreadLocal ,或者是通过把对象的特定于线程的状态封装进 ThreadLocal 。例如,在与数据库有紧密联系的应用程序中,程序的很多方法可能都需要访问数据库。在系统的每个方法中都包含一个 Connection 作为参数是不方便的 用“单子”来访问连接可能是一个虽然更粗糙,但却方便得多的技术。然而,多个线程不能安全地共享一个 JDBC Connection 。如清单 3 所示,通过使用“单子”中的 ThreadLocal ,我们就能让我们的程序中的任何类容易地获取每线程 Connection 的一个引用。这样,我们可以认为 ThreadLocal 允许我们创建 每线程单子。 清单 3. 把一个 JDBC 连接存储到一个每线程 Singleton 中 public class ConnectionDispenser private static class ThreadLocalConnection extends ThreadLocal public Object initialValue() return DriverManager.getConnection(ConfigurationSingleton.getDbUrl(); private ThreadLocalConnection conn = new ThreadLocalConnection(); public static Connection getConnection() return (Connection) conn.get(); 任何创建的花费比使用的花费相对昂贵些的有状态或非线程安全的对象,例如 JDBC Connection 或正则表达式匹配器,都是可以使用每线程单子(singleton)技术的好地方。当然,在类似这样的地方,您可以使用其它技术,例如用池,来安全地管理 共享访问。然而,从可伸缩性角度看,即使是用池也存在一些潜在缺陷。因为池实现必须使用同步,以维护池数据结构的完整性,如果所有线程使用同一个池,那么 在有很多线程频繁地对池进行访问的系统中,程序性能将因争用而降低。 用 ThreadLocal 简化调试日志纪录其它适合使用 ThreadLocal 但用池却不能成为很好的替代技术的应用程序包括存储或累积每线程上下文信息以备稍后检索之用这样的应用程序。例如,假设您想创建一个用于管理多线程应用程序调试信息的工具。您可以用如清单 4 所示的 DebugLogger 类作为线程局部容器来累积调试信息。在一个工作单元的开头,您清空容器,而当一个错误出现时,您查询该容器以检索这个工作单元迄今为止生成的所有调试信息。 清单 4. 用 ThreadLocal 管理每线程调试日志 public class DebugLogger private static class ThreadLocalList extends ThreadLocal public Object initialValue() return new ArrayList(); public List getList() return (List) super.get(); private ThreadLocalList list = new ThreadLocalList(); private static String stringArray = new String0; public void clear() list.getList().clear(); public void put(String text) list.getList().add(text); public String get() return list.getList().toArray(stringArray); 在您的代码中,您可以调用 DebugLogger.put() 来保存您的程序正在做什么的信息,而且,稍后如果有必要(例如发生了一个错误),您能够容易地检索与某个特定线程相关的调试信息。 与简单地把所有信息转储到一个日志文件,然后努力找出哪个日志记录来自哪个线程(还要担心线程争用日志纪录对象)相比,这种技术简便得多,也有效得多。 ThreadLocal 在基于 servlet 的应用程序或工作单元是一个整体请求的任何多线程应用程序服务器中也是很有用的,因为在处理请求的整个过程中将要用到单个线程。您可以通过前面讲述的每线程单子技术用 ThreadLocal 变量来存储各种每请求(per-request)上下文信息。 ThreadLocal 的线程安全性稍差的堂兄弟,InheritableThreadLocalThreadLocal 类有一个亲戚,InheritableThreadLocal,它以相似的方式工作,但适用于种类完全不同的应用程序。创建一个线程时如果保存了所有 InheritableThreadLocal 对象的值,那么这些值也将自动传递给子线程。如果一个子线程调用 InheritableThreadLocal 的 get() ,那么它将与它的父线程看到同一个对象。为保护线程安全性,您应该只对不可变对象(一旦创建,其状态就永远不会被改变的对象)使用 InheritableThreadLocal ,因为对象被多个线程共享。 InheritableThreadLocal 很合适用于把数据从父线程传到子线程,例如用户标识(user id)或事务标识(transaction id),但不能是有状态对象,例如 JDBC Connection 。 ThreadLocal 的性能虽然线程局部变量早已赫赫有名并被包括 Posix pthreads 规范在内的很多线程框架支持,但最初的 Java 线程设计中却省略了它,只是在 Java 平台的版本 1.2 中才添加上去。在很多方面, ThreadLocal 仍在发展之中;在版本 1.3 中它被重写,版本 1.4 中又重写了一次,两次都专门是为了性能问题。 在 JDK 1.2 中, ThreadLocal 的实现方式与清单 2 中的方式非常相似,除了用同步 WeakHashMap 代替 HashMap 来存储 values 之外。(以一些额外的性能开销为代价,使用 WeakHashMap 解决了无法对 Thread 对象进行垃圾回收的问题。)不用说, ThreadLocal 的性能是相当差的。 Java 平台版本 1.3 提供的 ThreadLocal 版本已经尽量更好了;它不使用任何同步,从而不存在可伸缩性问题,而且它也不使用弱引用。相反地,人们通过给 Thread 添加一个实例变量(该变量用于保存当前线程的从线程局部变量到它的值的映射的 HashMap )来修改 Thread 类以支持 ThreadLocal 。因为检索或设置一个线程局部变量的过程不涉及对可能被另一个线程读写的数据的读写操作,所以您可以不用任何同步就实现 ThreadLocal.get() 和 set() 。而且,因为每线程值的引用被存储在自已的 Thread 对象中,所以当对 Thread 进行垃圾回收时,也能对该 Thread 的每线程值进行垃圾回收。 不幸的是,即使有了这些改进,Java 1.3 中的 ThreadLocal 的性能仍然出奇地慢。据我的粗略测量,在双处理器 Linux 系统上的 Sun 1.3 JDK 中进行 ThreadLocal.get() 操作,所耗费的时间大约是无争用同步的两倍。性能这么差的原因是 Thread.currentThread() 方法的花费非常大,占了 ThreadLocal.get() 运行时间的三分之二还多。虽然有这些缺点,JDK 1.3 ThreadLocal.get() 仍然比争用同步快得多,所以如果在任何存在严重争用的地方(可能是有非常多的线程,或者同步块被频繁地执行,或者同步块很大), ThreadLocal 可能仍然要高效得多。 在 Java 平台的最新版本,即版本 1.4b2 中, ThreadLocal 和 Thread.currentThread() 的性能都有了很大提高。有了这些提高, ThreadLocal 应该比其它技术,如用池,更快。由于它比其它技术更简单,也更不易出错,人们最终将发现它是避免线程间出现不希望的交互的有效途径。 ThreadLocal 的好处ThreadLocal 能带来很多好处。它常常是把有状态类描绘成线程安全的,或者封装非线程安全类以使它们能够在多线程环境中安全地使用的最容易的方式。使用 ThreadLocal 使我们可以绕过为实现线程安全而对何时需要同步进行判断的复杂过程,而且因为它不需要任何同步,所以也改善了可伸缩性。除简单之外,用 ThreadLocal 存储每线程单子或每线程上下文信息在归档方面还有一个颇有价值好处 通过使用 ThreadLocal ,存储在 ThreadLocal 中的对象都是 不被线程共享的是清晰的,从而简化了判断一个类是否线程安全的工作。 我希望您从这个系列中得到了乐趣,也学到了知识,我也鼓励您到我的 讨论论坛中来深入研究多线程问题。 posted 2007-04-09 20:52 阿成 阅读(1067) | 评论 (1) |编辑收藏 Web服务器开发环境下的线程安全问题 Servlet是在多线程环境下的。即可能有多个请求发给一个servelt实例,每个请求是一个线程。 struts下的action也 类似,同样在多线程环境下。可以参考struts user guide: /struts-action/userGuide /building_controller.html 中的Action Class Design Guidelines一节: Write code for a multi-threaded environment - Our controller servlet creates only one instance of your Action class, and uses this one instance to service all requests. Thus, you need to write thread-safe Action classes. Follow the same guidelines you would use to write thread-safe Servlets. 译:为多线程环境编写代码。我们的controller servlet指挥创建你的Action 类的一个实例,用此实例来服务所有的请求。因此,你必须编写线程安全的Action类。遵循与写线程安全的servlet同样的方针。 1.什么是线程安全的代码 在多线程环境下能正确执行的代码就是线程安全的。 安全的意思是能正确执行,否则后果是程序执行错误,可能出现各种异常情况。2.如何编写线程安全的代码 很多书籍里都详细讲解了如何这方面的问题,他们主要讲解的是如何同步线程对共享资源的使用的问题。主要是对synchronized关键字的各种用法,以及锁的概念。 Java1.5中也提供了如读写锁这类的工具类。这些都需要较高的技巧,而且相对难于调试。 但是,线程同步是不得以的方法,是比较复杂的,而且会带来性能的损失。等效的代码中,不需要同步在编写容易度和性能上会更好些。 我这里强调的是什么代码是始终为线程安全的、是不需要同步的。如下: 1)常量始终是线程安全的,因为只存在读操作。 2)对构造器的访问(new 操作)是线程安全的,因为每次都新建一个实例,不会访问共享的资源。 3)最重要的是:局部变量是线程安全的。因为每执行一个方法,都会在独立的空间创建局部变量,它不是共享的资源。局部变量包括方法的参数变量。 struts user guide里有: Only Use Local Variables - The most important principle that aids in thread-safe coding is to use only local variables, not instance variables , in your Action class. 译:只使用用局部变量。-编写线程安全的代码最重要的原则就是,在Action类中只使用局部变量,不使用实例变量。总结: 在Java的Web服务器环境下开发,要注意线程安全的问题。最简单的实现方式就是在Servlet和Struts Action里不要使用类变量、实例变量,但可以使用类常量和实例常量。 如果有这些变量,可以将它们转换为方法的参数传入,以消除它们。 注意一个容易混淆的地方:被Servlet或Action调用的类中(如值对象、领域模型类)中是否可以安全的使用实例变量?如果你在每次方法调用时 新建一个对象,再调用它们的方法,则不存在同步问题-因为它们不是多个线程共享的资源,只有共享的资源才需要同步-而Servlet和Action的实例对于多个线程是共享的。 换句话说,Servlet和Action的实例会被多个线程同时调用,而过了这一层,如果在你自己的代码中没有另外启动线程,且每次调用后续业务对象时都是先新建一个实例再调用,则都是线程安全的。 深入浅出ThreadLocal一、ThreadLocal概述 学习JDK中的类,首先看下JDK API对此类的描述,描述如下:JDK API 写道该类提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal 实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。API表达了下面几种观点:1、ThreadLocal不是线程,是线程的一个变量,你可以先简单理解为线程类的属性变量。2、ThreadLocal 在类中通常定义为静态类变量。3、每个线程有自己的一个ThreadLocal,它是变量的一个拷贝,修改它不影响其他线程。 既然定义为类变量,为何为每个线程维护一个副本(姑且成为拷贝容易理解),让每个线程独立访问?多线程编程的经验告诉我们,对于线程共享资源(你可以理解为属性),资源是否被所有线程共享,也就是说这个资源被一个线程修改是否影响另一个线程的运行,如果影响我们需要使用synchronized同步,让线程顺序访问。 ThreadLocal适用于资源共享但不需要维护状态的情况,也就是一个线程对资源的修改,不影响另一个线程的运行;这种设计是空间换时间,synchronized顺序执行是时间换取空间。二、ThreadLocal方法介绍Tget()返回此线程局部变量的当前线程副本中的值。protected TinitialValue()返回此线程局部变量的当前线程的“初始值”。voidremove()移除此线程局部变量当前线程的值。voidset(Tvalue)将此线程局部变量的当前线程副本中的值设置为指定值。三、深入源码 ThreadLocal有一个ThreadLocalMap静态内部类,你可以简单理解为一个MAP,这个Map为每个线程复制一个变量的拷贝存储其中。 当线程调用ThreadLocal.get()方法获取变量时,首先获取当前线程引用,以此为key去获取响应的ThreadLocalMap,如果此Map不存在则初始化一个,否则返回其中的变量,代码如下:Get方法代码 1. publicTget() 2. Threadt=Thread.currentThread(); 3. ThreadLocalMapmap=getMap(t); 4. if(map!=null) 5. ThreadLocalMap.Entrye=map.getEntry(this); 6. if(e!=null) 7. return(T)e.value; 8. 9. returnsetInitialValue(); 10. public T get() Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) return (T)e.value; return setInitialValue(); 调用get方法如果此Map不存在首先初始化,创建此map,将线程为key,初始化的vlaue存入其中,注意此处的initialValue,我们可以覆盖此方法,在首次调用时初始化一个适当的值。setInitialValue代码如下:Java代码 1. privateTsetInitialValue() 2. Tvalue=initialValue(); 3. Threadt=Thread.currentThread(); 4. ThreadLocalMapmap=getMap(t); 5. if(map!=null) 6. map.set(this,value); 7. else 8. createMap(t,value); 9. returnvalue; 10. private T setInitialValue() T value = initialValue(); Thread t = Thread.currentThread(); set方法相对比较简单如果理解以上俩个方法,获取当前线程的引用,从map中获取该线程对应的map,如果map存在更新缓存值,否则创建并存储,代码如下:Java代码 1. publicvoidset(Tvalue) 2. Threadt=Thread.currentThread(); 3. ThreadLocalMapmap=getMap(t); 4. if(map!=null) 5. map.set(this,value); 6. else 7. createMap(t,value); 8. public void set(T value) Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); 四、ThreadLocal应用示例 以代码来直观说明之Java代码 1. importjava.text.DateFormat; 2. importjava.text.ParseException; 3. importjava.text.SimpleDateFormat; 4. importjava.util.Date; 5. 6. publicclassDateUtil 7. 8. privatestaticfinalStringDATE_FORMAT=yyyy-MM-ddHH:mm:ss; 9. 10. SuppressWarnings(rawtypes) 11. privatestaticThreadLocalthreadLocal=newThreadLocal() 12. protectedsynchronizedObjectinitialValue() 13. returnnewSimpleDateFormat(DATE_FORMAT); 14. 15. ; 16. 17. publicstaticDateFormatgetDateFormat() 18. return(DateFormat)threadLocal.get(); 19. 20. 21. publicstaticDateparse(StringtextDate)throwsParseException 22. returngetDateFormat().parse(textDate); 23. 24. import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; public class DateUtil private static final String DATE_FORMAT = yyyy-MM-dd HH:
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 小学数学阅读能力培养计划
- 小学一年级上册语文阅读计划
- 湖南美术出版社二年级下册美术能力分层教学计划
- 财经类数据分析研究性学习报告范文
- 制药企业质量管理体系与保障措施
- 体育场馆施工组织方案和技术措施
- 信息化项目质量进度造价安全文明监理措施
- 市场运营管理专业实习总结范文
- 危重疑难病例讨论病例筛选流程
- 电影拍摄现场猝死应急流程
- 卫生院常见护理常规
- 2025年全国矿山安全生产事故情况
- 2024年北京市西城区第十五中学七上数学期末检测模拟试题含解析
- 2025年环境监测试验检测人员培训计划
- 市政项目成本测算手册2023版
- 中华人民共和国监察法2025修订版实施条例试卷试题含答案
- 皮肤激光设备管理制度
- Unit 1 Happy Holiday 第1课时(Section A 1a-1d) 2025-2026学年人教版英语八年级下册
- Q-SY 13034-2024 物料主数据数字化描述规范
- 外墙工程维修协议书
- 2025年中国船舶代理项目投资可行性研究报告
评论
0/150
提交评论