摘录:线程的私有成员变量和ThreadLocal
本文摘录自Java并发编程—设计原则与模式的2.3.2.1至2.3.2.3节。Java并发编程—设计原则与模式真的是一本很好的书,推荐给每一位想学习Java并发编程的程序员。
2.3.2.1线程私有成员变量
线程中执行的方法调用除了要接收受限制的引用之外,还可以访问代表它们正在运行的Thread对象,以及由此可以访问到的更多信息。静态方法Thread.currentThread()可以被任何方法调用,并且返回调用者的Thread对象。
程序员可以利用这个特性,在Thread的子类中增加成员变量,并提供只在本线程内部访问这些成员变量的方法。例如:
class ThreadWithOutputStream extends Thread { private OutputStream output; ThreadWithOutputStream(Runnable r, OutputStream s) { super(r); output = s; } static ThreadWithOutputStream current() throws ClassCastException { return (ThreadWithOutputStream) (currentThread()); } static OutputStream getOutput() { return current().output; } static void setOutput(OutputStream s) { current().output =s;} }
这个类可以如下使用:
class ServiceUsingThreadWithOutputStream { // Fragments // ... public void service() throws IOException { OutputStream output = new FileOutputStream("..."); Runnable r = new Runnable() { public void run() { try { doService(); } catch (IOException e) { ... } } }; new ThreadWithOutputStream(r, output).start(); } void doService() throws IOException { ThreadWithOutputStream.current().getOutput().write(...); // ... } }
2.3.2.2ThreadLocal
java.lang.ThreadLocal工具类排除了使用私有线程技术的一个障碍,这个障碍就是对特定Thread子类的依赖。java.lang.ThreadLocal工具类使得线程私有变量可以以特殊的形式增加到任意一段代码中。
TheadLocal类的内部维护了一张相关数据(Object引用)和Thread实例的表。ThreadLocal类中的set和get方法可以存取当前Thread控制的数据。从ThreadLocal类继承来的java.lang.InheriableThreadLocal类可以自动把本线程的变量传递给其创建的任何一个线程。
很多使用ThreadLocal的设计都被视为单态的扩展。多数的ThreadLocal应用程序为每个线程创建了一个资源实例,而不是每个为每个程序创建一个。ThreadLocal的变量被声明为静态的,并且是在包范围内可见,所以这个变量可以在运行于某个线程的一组方法中访问。
ThreadLocal可以以如下的这种方式运行在我们的程序中:
class ServiceUsingThreadLocal { // Fragments static ThreadLocal output = new ThreadLocal(); public void service() { try { final OutputStream s = new FileOutputStream("..."); Runnable r = new Runnable() { public void run() { output.set(s); try { doService(); } catch (IOException e) { ... } finally { try { s.close(); } catch (IOException ignore) {} } } }; new Thread(r).start(); } catch (IOException e) { ...} } void doService() throws IOException { ((OutputStream)(output.get())).write(...); // ... } }
2.3.2.3 应用和结果
拥有线程私有数据的ThreadLocal和Thread子类,一般只在没有更好选择的情况下选用。与其他设计相比(例如,基于会话的设计),其优缺点包括:
- 把对象引用放在Thread对象内部(或者与其关联),使得运行于同一个线程的方法可以共享这些引用,而不需要以参数的形式传递。在维护诸如当前线程的AccessControlContext(类似于java.security包中那样)等上下文信息,或者为打开相关文件而保存的当前工作目录时,使用这种线程局部的方式是一个很好的选择。ThreadLocal还可以用来创建每个线程的资源池。
- 使用线程私有变量会隐藏影响行为的参数,这使得更加难以进行错误或遗漏检查。从这个意义上说,线程私有数据存在着和静态全局变量同样的可追踪性问题,尽管没有静态全局变量那么严重。
- 保证线程私有数据状态的改变(例如,关闭一个输出文件,打开另一个)会影响所有相关的代码。这点实现起来很简单,但是保证所有这些改变间的相互协调确实很难的。
- 线程内部对线程私有变量进行读写时不需要同步。但是通过currentThread或内部的ThreadLocal表的访问路径不比没有竞争时的同步方法要付出的代价低。所以,只有在对象需要共享且在对个线程间竞争使用该对象的情况下,改用线程私有数据技术才有可能提高系统的整体性能。
- 使用线程私有数据会增加代码的依赖关系,从而降低代码的重用性。在利用Thread子类的时候,该问题更加突出。例如,doService只有运行在ThreadWithOutputStream类型的线程中的时候才是可用的。如果不在这种情况下使用,调用current方法会引起ClassCastException异常。
- 通过ThreadLocal增加上下文信息有时是惟一可以使组件和不在调用序列间传递信息的现有代码协调工作的方法。
- 轻量级可执行程序框架只是间接地基于Thread类,尤其是工作者线程池。所以,在轻量级可执行程序框架中很难把相关数据和执行上下文联合起来。
