📄 轻松使用线程:不共享有时是最好的.htm
字号:
<TR>
<TD><A
href="http://www-900.ibm.com/developerWorks/cn/java/j-threads/index3.shtml#h9285">实现每线程
Singleton</A></TD></TR>
<TR>
<TD><A
href="http://www-900.ibm.com/developerWorks/cn/java/j-threads/index3.shtml#h11704">简化调试日志纪录</A></TD></TR>
<TR>
<TD><A
href="http://www-900.ibm.com/developerWorks/cn/java/j-threads/index3.shtml#h14240">InheritableThreadLocal</A></TD></TR>
<TR>
<TD><A
href="http://www-900.ibm.com/developerWorks/cn/java/j-threads/index3.shtml#h15292">ThreadLocal
的性能</A></TD></TR>
<TR>
<TD><A
href="http://www-900.ibm.com/developerWorks/cn/java/j-threads/index3.shtml#h18690">ThreadLocal
的好处</A></TD></TR><!--Standard links for every article -->
<TR>
<TD><A
href="http://www-900.ibm.com/developerWorks/cn/java/j-threads/index3.shtml#resources">参考资料</A></TD></TR>
<TR>
<TD><A
href="http://www-900.ibm.com/developerWorks/cn/java/j-threads/index3.shtml#author1">关于作者</A></TD></TR>
<TR>
<TD><A
href="http://www-900.ibm.com/developerWorks/cn/java/j-threads/index3.shtml#rating">对本文的评价</A></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE><!-- End TOC--><!-- Start Related Content Area-->
<TABLE cellSpacing=0 cellPadding=0 width=160 border=0>
<TBODY>
<TR>
<TD width=160 bgColor=#000000 height=1><IMG height=1 alt=""
src="轻松使用线程:不共享有时是最好的.files/c.gif" width=160></TD></TR>
<TR>
<TD align=middle background=轻松使用线程:不共享有时是最好的.files/bg-gold.gif
height=5><B>相关内容:</B></TD></TR>
<TR>
<TD width=160 bgColor=#666666 height=1><IMG height=1 alt=""
src="轻松使用线程:不共享有时是最好的.files/c.gif" width=160></TD></TR>
<TR>
<TD align=right>
<TABLE cellSpacing=0 cellPadding=3 width="98%" border=0>
<TBODY>
<TR>
<TD><A
href="http://www-900.ibm.com/developerWorks/cn/java/j-thread/index.shtml">编写多线程的
Java 应用程序</A></TD></TR>
<TR>
<TD><A
href="http://www-900.ibm.com/developerWorks/cn/java/j-pool/index.shtml">连接池</A></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE><!-- End TOC--><!-- Start Related Content Area-->
<TABLE cellSpacing=0 cellPadding=0 width=160 border=0>
<TBODY>
<TR>
<TD width=160 bgColor=#000000 height=1><IMG height=1 alt=""
src="轻松使用线程:不共享有时是最好的.files/c.gif" width=160></TD></TR>
<TR>
<TD align=middle background=轻松使用线程:不共享有时是最好的.files/bg-gold.gif
height=5><A class=nav
href="http://www-900.ibm.com/developerWorks/cn/java/index.shtml"><B>在
Java 专区中还有:</B></A></TD></TR>
<TR>
<TD width=160 bgColor=#666666 height=1><IMG height=1 alt=""
src="轻松使用线程:不共享有时是最好的.files/c.gif" width=160></TD></TR>
<TR>
<TD align=right>
<TABLE cellSpacing=0 cellPadding=3 width="98%" border=0>
<TBODY>
<TR>
<TD><A
href="http://www-900.ibm.com/developerWorks/cn/cnedu.nsf/java-onlinecourse-bytitle?OpenView&Count=500">教程</A></TD></TR>
<TR>
<TD><A
href="http://www-900.ibm.com/developerWorks/cn/cntools.nsf/dw/java-all-byname?OpenDocument&Count=500">工具和产品</A></TD></TR>
<TR>
<TD><A
href="http://www-900.ibm.com/developerWorks/cn/cntools.nsf/dw/java-beans-bytitle?OpenDocument&Count=500">代码和组件</A></TD></TR>
<TR>
<TD><A
href="http://www-900.ibm.com/developerWorks/cn/cnpapers.nsf/java-papers-bynewest?OpenView&Count=500">文章</A></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE><!-- End Related dW Content Area-->
<TABLE cellSpacing=0 cellPadding=0 width=160 border=0>
<TBODY>
<TR>
<TD width=150 bgColor=#000000 colSpan=2 height=2><IMG height=2
alt="" src="轻松使用线程:不共享有时是最好的.files/c.gif" width=160></TD></TR>
<TR>
<TD width=150 bgColor=#ffffff colSpan=2 height=2><IMG height=2
alt="" src="轻松使用线程:不共享有时是最好的.files/c.gif"
width=160></TD></TR></TBODY></TABLE><!-- END STANDARD SIDEBAR AREA--></TD></TR></TBODY></TABLE><SPAN
class=atitle2>利用 ThreadLocal 提高可伸缩性</SPAN>
<P><A
href="http://www-900.ibm.com/developerWorks/cn/java/j-threads/index3.shtml#author1">Brian
Goetz</A> (<A
href="mailto:brian@quiotix.com">brian@quiotix.com</A>)<BR>软件顾问,Quiotix
Corp.<BR>2001 10 月</P>
<BLOCKQUOTE><CODE>ThreadLocal</CODE> 类是悄悄地出现在 Java 平台版本 1.2
中的。虽然支持线程局部变量早就是许多线程工具(例如 Posix <CODE>pthreads</CODE> 工具)的一部分,但 Java
Threads API
的最初设计却没有这项有用的功能。而且,最初的实现也相当低效。由于这些原因,<CODE>ThreadLocal</CODE>
极少受到关注,但对简化线程安全并发程序的开发来说,它却是很方便的。在<I>轻松使用线程</I>的第 3 部分,Java 软件顾问 Brian
Goetz 研究了 <CODE>ThreadLocal</CODE> 并提供了一些使用技巧。
<P>参加 Brian 的<A
href="http://www-105.ibm.com/developerWorks/java_df.nsf/AllViewTemplate?OpenForm&RestrictToCategory=23">多线程
Java 编程讨论论坛</A>以获得您工程中的线程和并发问题的帮助。</P></BLOCKQUOTE>
<P>编写线程安全类是困难的。它不但要求仔细分析在什么条件可以对变量进行读写,而且要求仔细分析其它类能如何使用某个类。
有时,要在不影响类的功能、易用性或性能的情况下使类成为线程安全的是很困难的。有些类保留从一个方法调用到下一个方法调用的状态信息,要在实践中使这样的类成为线程安全的是困难的。</P>
<P>管理非线程安全类的使用比试图使类成为线程安全的要更容易些。非线程安全类通常可以安全地在多线程程序中使用,只要您能确保一个线程所用的类的实例不被其它线程使用。例如,JDBC
<CODE>Connection</CODE> 类是非线程安全的 — 两个线程不能在小粒度级上安全地共享一个
<CODE>Connection</CODE> — 但如果每个线程都有它自己的
<CODE>Connection</CODE>,那么多个线程就可以同时安全地进行数据库操作。</P>
<P>不使用 <CODE>ThreadLocal</CODE> 为每个线程维护一个单独的 JDBC 连接(或任何其它对象)当然是可能的;Thread
API 给了我们把对象和线程联系起来所需的所有工具。而 ThreadLocal
则使我们能更容易地把线程和它的每线程(per-thread)数据成功地联系起来。</P>
<P><A name=1></A><A name=h5450><SPAN class=atitle2>什么是线程局部变量(thread-local
variable)?</SPAN></A><BR><I>线程局部变量</I>高效地为每个使用它的线程提供单独的线程局部变量值的副本。每个线程只能看到与自己相联系的值,而不知道别的线程可能正在使用或修改它们自己的副本。一些编译器(例如
Microsoft Visual C++ 编译器或 IBM XL FORTRAN 编译器)用存储类别修饰符(像
<CODE>static</CODE> 或 <CODE>volatile</CODE>)把对线程局部变量的支持集成到了其语言中。Java
编译器对线程局部变量不提供特别的语言支持;相反地,它用 <CODE>ThreadLocal</CODE> 类实现这些支持,<CODE>核心
Thread</CODE> 类中有这个类的特别支持。</P>
<P>因为线程局部变量是通过一个类来实现的,而不是作为 Java 语言本身的一部分,所以 Java
语言线程局部变量的使用语法比内建线程局部变量语言的使用语法要笨拙一些。要创建一个线程局部变量,请实例化类
<CODE>ThreadLocal</CODE> 的一个对象。 <CODE>ThreadLocal</CODE> 类的行为与
<CODE>java.lang.ref</CODE> 中的各种 <CODE>Reference</CODE>
类的行为很相似;<CODE>ThreadLocal</CODE> 类充当存储或检索一个值时的间接句柄。清单 1 显示了
<CODE>ThreadLocal</CODE> 接口。</P>
<P><A name=listing1><B>清单 1. ThreadLocal 接口</B></A>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc
border=1><TBODY>
<TR>
<TD><PRE><CODE>
public class ThreadLocal {
public Object get();
public void set(Object newValue);
public Object initialValue();
}
</CODE></PRE></TD></TR></TBODY></TABLE></P>
<P></P>
<P><CODE>get()</CODE> 访问器检索变量的当前线程的值;<CODE>set()</CODE>
访问器修改当前线程的值。<CODE>initialValue()</CODE>
方法是可选的,如果线程未使用过某个变量,那么您可以用这个方法来设置这个变量的初始值;它允许延迟初始化。用一个示例实现来说明 ThreadLocal
的工作方式是最好的方法。清单 2 显示了 <CODE>ThreadLocal</CODE>
的一个实现方式。它不是一个特别好的实现(虽然它与最初实现非常相似),所以很可能性能不佳,但它清楚地说明了 ThreadLocal
的工作方式。</P>
<P><A name=listing1><B>清单 2. ThreadLocal 的糟糕实现</B></A>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc
border=1><TBODY>
<TR>
<TD><PRE><CODE>
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;
}
}
</CODE></PRE></TD></TR></TBODY></TABLE></P>
<P></P>
<P>这个实现的性能不会很好,因为每个 <CODE>get()</CODE> 和 <CODE>set()</CODE> 操作都需要
<CODE>values</CODE> 映射表上的同步,而且如果多个线程同时访问同一个
<CODE>ThreadLocal</CODE>,那么将发生争用。此外,这个实现也是不切实际的,因为用 <CODE>Thread</CODE>
对象做 <CODE>values</CODE> 映射表中的关键字将导致无法在线程退出后对 <CODE>Thread</CODE>
进行垃圾回收,而且也无法对死线程的 <CODE>ThreadLocal</CODE> 的特定于线程的值进行垃圾回收。</P>
<P><A name=2></A><A name=h9285><SPAN class=atitle2>用 ThreadLocal 实现每线程
Singleton</SPAN></A><BR>线程局部变量常被用来描绘有状态“单子”(Singleton)
或线程安全的共享对象,或者是通过把不安全的整个变量封装进 <CODE>ThreadLocal</CODE>,或者是通过把对象的特定于线程的状态封装进
<CODE>ThreadLocal</CODE>。例如,在与数据库有紧密联系的应用程序中,程序的很多方法可能都需要访问数据库。在系统的每个方法中都包含一个
<CODE>Connection</CODE> 作为参数是不方便的 —
用“单子”来访问连接可能是一个虽然更粗糙,但却方便得多的技术。然而,多个线程不能安全地共享一个 JDBC
<CODE>Connection</CODE>。如清单 3 所示,通过使用“单子”中的
<CODE>ThreadLocal</CODE>,我们就能让我们的程序中的任何类容易地获取每线程 <CODE>Connection</CODE>
的一个引用。这样,我们可以认为 <CODE>ThreadLocal</CODE> 允许我们创建<I>每线程单子</I>。</P>
<P><A name=listing3><B>清单 3. 把一个 JDBC 连接存储到一个每线程 Singleton 中</B></A>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc
border=1><TBODY>
<TR>
<TD><PRE><CODE>
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();
}
}
</CODE></PRE></TD></TR></TBODY></TABLE></P>
<P></P>
<P>任何创建的花费比使用的花费相对昂贵些的有状态或非线程安全的对象,例如 JDBC <CODE>Connection</CODE>
或正则表达式匹配器,都是可以使用每线程单子(singleton)技术的好地方。当然,在类似这样的地方,您可以使用其它技术,例如用池,来安全地管理共享访问。然而,从可伸缩性角度看,即使是用池也存在一些潜在缺陷。因为池实现必须使用同步,以维护池数据结构的完整性,如果所有线程使用同一个池,那么在有很多线程频繁地对池进行访问的系统中,程序性能将因争用而降低。</P>
<P><A name=3></A><A name=h11704><SPAN class=atitle2>用 ThreadLocal
简化调试日志</SPAN></A>纪录<BR>其它适合使用 <CODE>ThreadLocal</CODE>
但用池却不能成为很好的替代技术的应用程序包括存储或累积每线程上下文信息以备稍后检索之用这样的应用程序。例如,假设您想创建一个用于管理多线程应用程序调试信息的工具。您可以用如清单
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -