对threadlocal线程局部变量的探讨 1.docx
《对threadlocal线程局部变量的探讨 1.docx》由会员分享,可在线阅读,更多相关《对threadlocal线程局部变量的探讨 1.docx(16页珍藏版)》请在冰豆网上搜索。
对threadlocal线程局部变量的探讨1
ThreadLocal线程局部变量
什么是线程局部变量
什么是线程局部变量(thread-localvariable)?
轻松使用线程:
不共享有时是最好的
ThreadLocal类是悄悄地出现在Java平台版本1.2中的。
虽然支持线程局部变量早就是许多线程工具(例如Posixpthreads工具)的一部分,但JavaThreadsAPI的最初设计却没有这项有用的功能。
而且,最初的实现也相当低效。
由于这些原因,ThreadLocal极少受到关注,但对简化线程安全并发程序的开发来说,它却是很方便的。
在轻松使用线程的第3部分,Java软件顾问BrianGoetz研究了ThreadLocal并提供了一些使用技巧。
编写线程安全类是困难的。
它不但要求仔细分析在什么条件可以对变量进行读写,而且要求仔细分析其它类能如何使用某个类。
有时,要在不影响类的功能、易用性或性能的情况下使类成为线程安全的是很困难的。
有些类保留从一个方法调用到下一个方法调用的状态信息,要在实践中使这样的类成为线程安全的是困难的。
管理非线程安全类的使用比试图使类成为线程安全的要更容易些。
非线程安全类通常可以安全地在多线程程序中使用,只要您能确保一个线程所用的类的实例不被其它线程使用。
例如,JDBCConnection类是非线程安全的—两个线程不能在小粒度级上安全地共享一个Connection—但如果每个线程都有它自己的Connection,那么多个线程就可以同时安全地进行数据库操作。
不使用ThreadLocal为每个线程维护一个单独的JDBC连接(或任何其它对象)当然是可能的;ThreadAPI给了我们把对象和线程联系起来所需的所有工具。
而ThreadLocal则使我们能更容易地把线程和它的每线程(per-thread)数据成功地联系起来。
什么是线程局部变量(thread-localvariable)?
线程局部变量高效地为每个使用它的线程提供单独的线程局部变量值的副本。
每个线程只能看到与自己相联系的值,而不知道别的线程可能正在使用或修改它们自己的副本。
一些编译器(例如MicrosoftVisualC++编译器或IBMXLFORTRAN编译器)用存储类别修饰符(像static或volatile)把对线程局部变量的支持集成到了其语言中。
Java编译器对线程局部变量不提供特别的语言支持;相反地,它用ThreadLocal类实现这些支持,核心Thread类中有这个类的特别支持。
因为线程局部变量是通过一个类来实现的,而不是作为Java语言本身的一部分,所以Java语言线程局部变量的使用语法比内建线程局部变量语言的使用语法要笨拙一些。
要创建一个线程局部变量,请实例化类ThreadLocal的一个对象。
ThreadLocal类的行为与java.lang.ref中的各种Reference类的行为很相似;ThreadLocal类充当存储或检索一个值时的间接句柄。
清单1显示了ThreadLocal接口。
清单1.ThreadLocal接口
publicclassThreadLocal{
publicObjectget();
publicvoidset(ObjectnewValue);
publicObjectinitialValue();
}
get()访问器检索变量的当前线程的值;set()访问器修改当前线程的值。
initialValue()方法是可选的,如果线程未使用过某个变量,那么您可以用这个方法来设置这个变量的初始值;它允许延迟初始化。
用一个示例实现来说明ThreadLocal的工作方式是最好的方法。
清单2显示了ThreadLocal的一个实现方式。
它不是一个特别好的实现(虽然它与最初实现非常相似),所以很可能性能不佳,但它清楚地说明了ThreadLocal的工作方式。
清单2.ThreadLocal的糟糕实现
publicclassThreadLocal{
privateMapvalues=Collections.synchronizedMap(newHashMap());
publicObjectget(){
ThreadcurThread=Thread.currentThread();
Objecto=values.get(curThread);
if(o==null&&!
values.containsKey(curThread)){
o=initialValue();
values.put(curThread,o);
}
returno;
}
publicvoidset(ObjectnewValue){
values.put(Thread.currentThread(),newValue);
}
publicObjectinitialValue(){
returnnull;
}
}
这个实现的性能不会很好,因为每个get()和set()操作都需要values映射表上的同步,而且如果多个线程同时访问同一个ThreadLocal,那么将发生争用。
此外,这个实现也是不切实际的,因为用Thread对象做values映射表中的关键字将导致无法在线程退出后对Thread进行垃圾回收,而且也无法对死线程的ThreadLocal的特定于线程的值进行垃圾回收。
用ThreadLocal实现每线程Singleton
线程局部变量常被用来描绘有状态“单子”(Singleton)或线程安全的共享对象,或者是通过把不安全的整个变量封装进ThreadLocal,或者是通过把对象的特定于线程的状态封装进ThreadLocal。
例如,在与数据库有紧密联系的应用程序中,程序的很多方法可能都需要访问数据库。
在系统的每个方法中都包含一个Connection作为参数是不方便的—用“单子”来访问连接可能是一个虽然更粗糙,但却方便得多的技术。
然而,多个线程不能安全地共享一个JDBCConnection。
如清单3所示,通过使用“单子”中的ThreadLocal,我们就能让我们的程序中的任何类容易地获取每线程Connection的一个引用。
这样,我们可以认为ThreadLocal允许我们创建每线程单子。
清单3.把一个JDBC连接存储到一个每线程Singleton中
publicclassConnectionDispenser{
privatestaticclassThreadLocalConnectionextendsThreadLocal{
publicObjectinitialValue(){
returnDriverManager.getConnection(ConfigurationSingleton.getDbUrl());
}
}
privateThreadLocalConnectionconn=newThreadLocalConnection();
publicstaticConnectiongetConnection(){
return(Connection)conn.get();
}
}
任何创建的花费比使用的花费相对昂贵些的有状态或非线程安全的对象,例如JDBCConnection或正则表达式匹配器,都是可以使用每线程单子(singleton)技术的好地方。
当然,在类似这样的地方,您可以使用其它技术,例如用池,来安全地管理共享访问。
然而,从可伸缩性角度看,即使是用池也存在一些潜在缺陷。
因为池实现必须使用同步,以维护池数据结构的完整性,如果所有线程使用同一个池,那么在有很多线程频繁地对池进行访问的系统中,程序性能将因争用而降低。
用ThreadLocal简化调试日志纪录
其它适合使用ThreadLocal但用池却不能成为很好的替代技术的应用程序包括存储或累积每线程上下文信息以备稍后检索之用这样的应用程序。
例如,假设您想创建一个用于管理多线程应用程序调试信息的工具。
您可以用如清单4所示的DebugLogger类作为线程局部容器来累积调试信息。
在一个工作单元的开头,您清空容器,而当一个错误出现时,您查询该容器以检索这个工作单元迄今为止生成的所有调试信息。
清单4.用ThreadLocal管理每线程调试日志
publicclassDebugLogger{
privatestaticclassThreadLocalListextendsThreadLocal{
publicObjectinitialValue(){
returnnewArrayList();
}
publicListgetList(){
return(List)super.get();
}
}
privateThreadLocalListlist=newThreadLocalList();
privatestaticString[]stringArray=newString[0];
publicvoidclear(){
list.getList().clear();
}
publicvoidput(Stringtext){
list.getList().add(text);
}
publicString[]get(){
returnlist.getList().toArray(stringArray);
}
}
在您的代码中,您可以调用DebugLogger.put()来保存您的程序正在做什么的信息,而且,稍后如果有必要(例如发生了一个错误),您能够容易地检索与某个特定线程相关的调试信息。
与简单地把所有信息转储到一个日志文件,然后努力找出哪个日志记录来自哪个线程(还要担心线程争用日志纪录对象)相比,这种技术简便得多,也有效得多。
ThreadLocal在基于servlet的应用程序或工作单元是一个整体请求的任何多线程应用程序服务器中也是很有用的,因为在处理请求的整个过程中将要用到单个线程。
您可以通过前面讲述的每线程单子技术用ThreadLocal变量来存储各种每请求(per-request)上下文信息。
ThreadLocal的线程安全性稍差的堂兄弟,InheritableThreadLocal
ThreadLocal类有一个亲戚,InheritableThreadLocal,它以相似的方式工作,但适用于种类完全不同的应用程序。
创建一个线程时如果保存了所有InheritableThreadLocal对象的值,那么这些值也将自动传递给子线程。
如果一个子线程调用InheritableThreadLocal的get(),那么它将与它的父线程看到同一个对象。
为保护线程安全性,您应该只对不可变对象(一旦创建,其状态就永远不会被改变的对象)使用InheritableThreadLocal,因为对象被多个线程共享。
InheritableThreadLocal很合适用于把数据从父线程传到子线程,例如用户标识(userid)或事务标识(transactionid),但不能是有状态对象,例如JDBCConnection。
ThreadLocal的性能
虽然线程局部变量早已赫赫有名并被包括Posixpthreads规范在内的很多线程框架支持,但最初的Java线程设计中却省略了它,只是在Java平台的版本1.2中才添加上去。
在很多方面,ThreadLocal仍在发展之中;在版本1.3中它被重写,版本1.4中又重写了一次,两次都专门是为了性能问题。
在JDK1.2中,ThreadLocal的实现方式与清单2中的方式非常相似,除了用同步WeakHashMap代替HashMap来存储values之外。
(以一些额外的性能开销为代价,使用WeakHashMap解决了无法对Thread对象进行垃圾回收的问题。
)不用说,ThreadLocal的性能是相当差的。
Java平台版本1.3提供的ThreadLocal版本已经尽量更好了;它不使用任何同步,从而不存在可伸缩性问题,而且它也不使用弱引用。
相反地,人们通过给Thread添加一个实例变量(该变量用于保存当前线程的从线程局部变量到它的值的映射的HashMap)来修改Thread类以支持ThreadLocal。
因为检索或设置一个线程局部变量的过程不涉及对可能被另一个线程读写的数据的读写操作,所以您可以不用任何同步就实现ThreadLocal.get()和set()。
而且,因为每线程值的引用被存储在自已的Thread对象中,所以当对Thread进行垃圾回收时,也能对该Thread的每线程值进行垃圾回收。
不幸的是,即使有了这些改进,Java1.3中的ThreadLocal的性能仍然出奇地慢。
据我的粗略测量,在双处理器Linux系统上的Sun1.3JDK中进行ThreadLocal.get()操作,所耗费的时间大约是无争用同步的两倍。
性能这么差的原因是Thread.currentThread()方法的花费非常大,占了ThreadLocal.get()运行时间的三分之二还多。
虽然有这些缺点,JDK1.3ThreadLocal.get()仍然比争用同步快得多,所以如果在任何存在严重争用的地方(可能是有非常多的线程,或者同步块被频繁地执行,或者同步块很大),ThreadLocal可能仍然要高效得多。
在Java平台的最新版本,即版本1.4b2中,ThreadLocal和Thread.currentThread()的性能都有了很大提高。
有了这些提高,ThreadLocal应该比其它技术,如用池,更快。
由于它比其它技术更简单,也更不易出错,人们最终将发现它是避免线程间出现不希望的交互的有效途径。
ThreadLocal的好处
ThreadLocal能带来很多好处。
它常常是把有状态类描绘成线程安全的,或者封装非线程安全类以使它们能够在多线程环境中安全地使用的最容易的方式。
使用ThreadLocal使我们可以绕过为实现线程安全而对何时需要同步进行判断的复杂过程,而且因为它不需要任何同步,所以也改善了可伸缩性。
除简单之外,用ThreadLocal存储每线程单子或每线程上下文信息在归档方面还有一个颇有价值好处—通过使用ThreadLocal,存储在ThreadLocal中的对象都是不被线程共享的是清晰的,从而简化了判断一个类是否线程安全的工作。
我希望您从这个系列中得到了乐趣,也学到了知识,我也鼓励您到我的讨论论坛中来深入研究多线程问题。
posted@2007-04-0920:
52阿成阅读(1067)|评论
(1)| 编辑 收藏
Web服务器开发环境下的线程安全问题
Servlet是在多线程环境下的。
即可能有多个请求发给一个servelt实例,每个请求是一个线程。
struts下的action也类似,同样在多线程环境下。
可以参考strutsuserguide:
http:
//struts.apache.org/struts-action/userGuide/building_controller.html中的ActionClassDesignGuidelines一节:
Writecodeforamulti-threadedenvironment-OurcontrollerservletcreatesonlyoneinstanceofyourActionclass,andusesthisoneinstancetoserviceallrequests.Thus,youneedtowritethread-safeActionclasses.Followthesameguidelinesyouwouldusetowritethread-safeServlets.
译:
为多线程环境编写代码。
我们的controllerservlet指挥创建你的Action类的一个实例,用此实例来服务所有的请求。
因此,你必须编写线程安全的Action类。
遵循与写线程安全的servlet同样的方针。
1.什么是线程安全的代码
在多线程环境下能正确执行的代码就是线程安全的。
安全的意思是能正确执行,否则后果是程序执行错误,可能出现各种异常情况。
2.如何编写线程安全的代码
很多书籍里都详细讲解了如何这方面的问题,他们主要讲解的是如何同步线程对共享资源的使用的问题。
主要是对synchronized关键字的各种用法,以及锁的概念。
Java1.5中也提供了如读写锁这类的工具类。
这些都需要较高的技巧,而且相对难于调试。
但是,线程同步是不得以的方法,是比较复杂的,而且会带来性能的损失。
等效的代码中,不需要同步在编写容易度和性能上会更好些。
我这里强调的是什么代码是始终为线程安全的、是不需要同步的。
如下:
1)常量始终是线程安全的,因为只存在读操作。
2)对构造器的访问(new操作)是线程安全的,因为每次都新建一个实例,不会访问共享的资源。
3)最重要的是:
局部变量是线程安全的。
因为每执行一个方法,都会在独立的空间创建局部变量,它不是共享的资源。
局部变量包括方法的参数变量。
strutsuserguide里有:
OnlyUseLocalVariables-Themostimportantprinciplethataidsinthread-safecodingistouseonlylocalvariables,notinstancevariables,inyourActionclass.
译:
只使用用局部变量。
--编写线程安全的代码最重要的原则就是,在Action类中只使用局部变量,不使用实例变量。
总结:
在Java的Web服务器环境下开发,要注意线程安全的问题。
最简单的实现方式就是在Servlet和StrutsAction里不要使用类变量、实例变量,但可以使用类常量和实例常量。
如果有这些变量,可以将它们转换为方法的参数传入,以消除它们。
注意一个容易混淆的地方:
被Servlet或Action调用的类中(如值对象、领域模型类)中是否可以安全的使用实例变量?
如果你在每次方法调用时
新建一个对象,再调用它们的方法,则不存在同步问题---因为它们不是多个线程共享的资源,只有共享的资源才需要同步---而Servlet和Action的实例对于多个线程是共享的。
换句话说,Servlet和Action的实例会被多个线程同时调用,而过了这一层,如果在你自己的代码中没有另外启动线程,且每次调用后续业务对象时都是先新建一个实例再调用,则都是线程安全的。
深入浅出ThreadLocal
一、ThreadLocal概述
学习JDK中的类,首先看下JDKAPI对此类的描述,描述如下:
JDKAPI写道
该类提供了线程局部(thread-local)变量。
这些变量不同于它们的普通对应物,因为访问某个变量(通过其get或set方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。
ThreadLocal实例通常是类中的privatestatic字段,它们希望将状态与某一个线程(例如,用户ID或事务ID)相关联。
API表达了下面几种观点:
1、ThreadLocal不是线程,是线程的一个变量,你可以先简单理解为线程类的属性变量。
2、ThreadLocal在类中通常定义为静态类变量。
3、每个线程有自己的一个ThreadLocal,它是变量的一个‘拷贝’,修改它不影响其他线程。
既然定义为类变量,为何为每个线程维护一个副本(姑且成为‘拷贝’容易理解),让每个线程独立访问?
多线程编程的经验告诉我们,对于线程共享资源(你可以理解为属性),资源是否被所有线程共享,也就是说这个资源被一个线程修改是否影响另一个线程的运行,如果影响我们需要使用synchronized同步,让线程顺序访问。
ThreadLocal适用于资源共享但不需要维护状态的情况,也就是一个线程对资源的修改,不影响另一个线程的运行;这种设计是‘空间换时间’,synchronized顺序执行是‘时间换取空间’。
二、ThreadLocal方法介绍
T
get()
返回此线程局部变量的当前线程副本中的值。
protected T
initialValue()
返回此线程局部变量的当前线程的“初始值”。
void
remove()
移除此线程局部变量当前线程的值。
void
set(T value)
将此线程局部变量的当前线程副本中的值设置为指定值。
三、深入源码
ThreadLocal有一个ThreadLocalMap静态内部类,你可以简单理解为一个MAP,这个‘Map’为每个线程复制一个变量的‘拷贝’存储其中。
当线程调用ThreadLocal.get()方法获取变量时,首先获取当前线程引用,以此为key去获取响应的ThreadLocalMap,如果此‘Map’不存在则初始化一个,否则返回其中的变量,代码如下:
Get方法代码
1.public T get() {
2. Thread t = Thread.currentThread();
3. ThreadLocalMap map = getMap(t);
4. if (map !
= null) {
5. ThreadLocalMap.Entry e = map.getEntry(this);
6. if (e !
= null)
7. return (T)e.value;
8. }
9. return setInitialValue();
10.}
publicTget(){Threadt=Thread.currentThread();ThreadLocalMapmap=getMap(t);if(map!
=null){ThreadLocalMap.Entrye=map.getEntry(this);if(e!
=null)return(T)e