《Java Concurrency in Practice》之线程封闭(Thread Confinement)

时间:2022-05-25 06:59:47

    当访问共享的可变数据时,通常需要使用同步。一种避免使用同步的方式就是不共享数据。如果仅在单线程内访问数据,就不需要同步。这种技术被称为是线程封闭(Thread Confinement),它是实现线程安全性的最简单方式之一。当某个对象封闭在一个线程中时,这种用法将自动实现线程安全性,即使被封闭的对象本身不是线程安全性的。
    Swing中大量使用了线程封闭技术。Swing的可视化组件和数据模型都不是线程安全的,Swing通过将它们封闭到Swing的事件分发线程中来实现线程安全性。
    线程封闭技术的另一种常见应用是JDBC(Java Database Connectivity)的Connection对象。JDBC规范并不要求Connection对象必须是线程安全的。在典型的服务器应用程序中,线程从连接池中获得一个Connection对象,并且用该对象来处理请求,使用完后再将对象返还给连接池。由于大多数请求都是由单个线程采用同步的方式来处理,并且在Connection对象返回之前,连接池不会再将它分配给其他线程,因此,这种连接管理模式在处理请求时隐含地将Connection对象封闭在线程中。
    3.3.1 Ad-hoc线程封闭
        Ad-hoc线程封闭是指,维护线程封闭性的职责完全由程序实现来承担。Ad-hoc线程封闭是非常脆弱的,因为没有任何一种语言特性,能将对象封闭到目标线程上。事实上,对线程封闭对象的引用通常保存在公有变量中。
        当决定使用线程封闭技术时,通常是因为要将某个特定的子系统实现为一个单线种子系统。在某些情况下,单线程子系统提供的简便性要胜过Ad-hoc线程封闭技术的脆弱性(使用单线程子系统的另一个原因是为了避免死锁,这也是大多数GUI框架都是单线种的原因)。
    3.3.2 栈封闭
        栈封闭是线程封闭的一种特例,在栈封闭中,只能通过局部变量才能访问对象。正如封闭能使得代码更容易维持不变性条件那样,同步变量也能使对象更易于封闭在线程中。它们位于执行线程的栈中,其他线程无法访问这个栈。栈封闭(也被称为线程内部使用或者线程局部使用,不要与核心类库中ThreadLocal混淆)比Ad-hoc线程封闭更易于维护,也更加健壮。
        在维持对象引用的栈封闭性时,程序员需要多做一些工作以确保被引用的对象不会逸出。如果在线程内部(Within-Thread)上下文中使用非线程安全的对象,那么该对象仍然是线程安全的。然而,要小心的是,只有编写代码的开发人员才知道那些对象需要被封闭到执行线程中,以及被封闭的对象是否是线程安全的。如果没有明确地说明这些需求,那么后续的维护人员很容易错误地使对象逸出。
    3.3.3 ThreadLocal类
        维持线程封闭性的一种更规范的方法是使用ThreadLocal,这个类能使线程中的某个值与保存值的对象关联起来。ThreadLocal提供了get与set等访问接口或方法,这些方法为每个使用该变量的线程都存有一份独立的副本,因此get总是返回由当前执行线程在调用set时设置的最新值。
        ThreadLocal对象通常用于防止对可变的单实例变量(Singleton)或全局变量进行共享。当某个频繁执行的操作需要一个临时对象,例如一个缓冲区,而同时又希望避免在每次执行时都重新分配该临时对象,就可以使用这项技术。
        例如,在Java 5.0之前,Integer.toString()方法使用ThreadLocal对象来保存一个12字节大小的缓冲区,用于对结果进行格式化,而不是使用共享的静态缓冲区(这需要使用锁机制)或者在每次调用时都分配一个新的缓冲区。