3.3. Thread Confinement(线程局限)

3.3. Thread Confinement(线程局限)
Accessing shared, mutable data requires using synchronization; one way to avoid this requirement is to not share. If data is only accessed from a single thread, no synchronization is needed. This technique, thread confinement, is one of the simplest ways to achieve thread safety. When an object is confined to a thread, such usage is automatically thread-safe even if the confined object itself is not [CPJ 2.3.2].
访问共享的、可变的数据需要用到同步机制。一种可以避免这种同步需求的方式是避免这种共享。如果数据只能被单线程的访问,那么同步机制就不需要了。线程局限技术就是这种简单的实现线程安全的技术。当一个对象被局限在一个线程内部使用的时候,尽管该对象本身可能不是线程安全的,但是这种用法可以实现线程安全。
Swing uses thread confinement extensively. The Swing visual components and data model objects are not thread safe; instead, safety is achieved by confining them to the Swing event dispatch thread. To use Swing properly, code running in threads other than the event thread should not access these objects. (To make this easier, Swing provides the invokeLater mechanism to schedule a Runnable for execution in the event thread.) Many concurrency errors in Swing applications stem from improper use of these confined objects from another thread.
Swing框架广泛的使用了线程局限技术。Swing框架的图形组件以及数据模型对象都不是线程安全的。线程安全性是通过把这些对象局限在Swing时间分发线程中来做到的。想要恰当的使用Swing框架的话,不是事件的线程不应该访问到这些对象(为了做到使用简单,Swing框架提供了invokeLater机制来调度事件线程中执行的Runnable对象)。很多Swing应用程序中的并发错误都可能是由其他线程对局限对象的不正确使用引起的。
Another common application of thread confinement is the use of pooled JDBC (Java Database Connectivity) Connection objects. The JDBC specification does not require that Connection objects be thread-safe.[9] In typical server applications, a thread acquires a connection from the pool, uses it for processing a single request, and returns it. Since most requests, such as servlet requests or EJB (Enterprise JavaBeans) calls, are processed synchronously by a single thread, and the pool will not dispense the same connection to another thread until it has been returned, this pattern of connection management implicitly confines the Connection to that thread for the duration of the request.
[9] The connection pool implementations provided by application servers are thread-safe; connection pools are necessarily accessed from multiple threads, so a non-thread-safe implementation would not make sense.
另外一个线程局限技术的常见应用是池化的JDBC连接对象。JDBC规范中并没有要求连接对象是线程安全的。在典型的服务器应用程序中,一个线程可能会从池中请求获取一个连接,使用该连接处理一个单独的请求,然后将该连接对象放回池中。由于大多数请求,例如servlet请求和EJB请求,都是在一个单独的线程中同步的运行的。在某一个线程被释放之前,线程池不会将该线程分配给其他链接。这种连接管理方式隐式的在请求的处理过程中,将连接局限在一个线程之内。
由应用服务器提供的连接池的实现是线程安全的,由于连接池需要在多线程的环境下被使用,因此如果实现不是线程安全的,则该实现没有任何意义。
Just as the language has no mechanism for enforcing that a variable is guarded by a lock, it has no means of confining an object to a thread. Thread confinement is an element of your program's design that must be enforced by its implementation. The language and core libraries provide mechanisms that can help in maintaining thread confinement local variables and the ThreadLocal class but even with these, it is still the programmer's responsibility to ensure that thread-confined objects do not escape from their intended thread.
就像Java语言本身没有为每一个变量都加上锁机制一样,Java语言也没有提供将一个对象局限在一个线程内的方法。因此,线程局限是你在程序设计时候的一个概念,这种概念取决于你的实现。虽然语言和核心类库提供可能保持线程局限性的本体类库和ThreadLocal类,但即便如此,保证线程局限的对象不会从他所在的线程中逃逸仍然是程序员的责任。
3.3.1. Ad-hoc Thread Confinement(非正式线程局限)
Ad-hoc thread confinement describes when the responsibility for maintaining thread confinement falls entirely on the implementation. Ad-hoc thread confinement can be fragile because none of the language features, such as visibility modifiers or local variables, helps confine the object to the target thread. In fact, references to thread-confined objects such as visual components or data models in GUI applications are often held in public fields.
Ad-hoc线程安全局限描述了维护线程局限的责任整个落在实现的时刻。由于没有利用任何的语言特性例如可见性修饰符或者本地变量,Ad-hoc线程局限可能是非常脆弱的。事实上,对于线程局限对象的应用(例如图形组件或者GUI应用中的数据模型)通常都是在public域中保持的。
The decision to use thread confinement is often a consequence of the decision to implement a particular subsystem, such as the GUI, as a single-threaded subsystem. Single-threaded subsystems can sometimes offer a simplicity benefit that outweighs the fragility of ad-hoc thread confinement.[10]
[10] Another reason to make a subsystem single-threaded is deadlock avoidance; this is one of the primary reasons most GUI frameworks are single-threaded. Single-threaded subsystems are covered in Chapter 9.
决定使用线程局限技术通常是想要将一个特定的子系统(例如GUI)实现成单线程子系统的结果。有时候单线程子系统提供的简单性有时候可以压过由于特别的线程局限所带来的脆弱性。另外一个经常要将子系统单线程化的原因是死锁的避免。这是大多数的GUI框架都是单线程的主要原因之一。单线程子系统将会在第九章进行讨论。
A special case of thread confinement applies to volatile variables. It is safe to perform read-modify-write operations on shared volatile variables as long as you ensure that the volatile variable is only written from a single thread. In this case, you are confining the modification to a single thread to prevent race conditions, and the visibility guarantees for volatile variables ensure that other threads see the most up-to-date value.
一个特别的线程局限的例子是volatile类型变量。只要你能够保证对volatile类型变量的修改是在单线程中完成的,那么对于共享volatile类型变量的read-modify-write操作就是安全的。在这个例子中,实际上就是采用将修改局限在单线程中来防止条件竞争的。Volatile类型的可见性保证可以确保其他线程看到最新的值。
Because of its fragility, ad-hoc thread confinement should be used sparingly; if possible, use one of the stronger forms of thread confinment (stack confinement or ThreadLocal) instead.
由于ad-hoc线程局限的脆弱性,应该限制该技术的使用。如果可能,应该使用形式更强的局限化技术来取代该技术。
3.3.2. Stack Confinement(栈局限)
Stack confinement is a special case of thread confinement in which an object can only be reached through local variables. Just as encapsulation can make it easier to preserve invariants, local variables can make it easier to confine objects to a thread. Local variables are intrinsically confined to the executing thread; they exist on the executing thread's stack, which is not accessible to other threads. Stack confinement (also called within-thread or thread-local usage, but not to be confused with the THReadLocal library class) is simpler to maintain and less fragile than ad-hoc thread confinement.
栈局限是一种线程局限的特殊形式,这种局限方式使得只能通过本地变量才能访问某个对象。如同封装能够很容易地维护不变性,本地变量可以方便地将一个对象局限在一个线程中。本地变量从本质上就是被局限在正在执行的线程之中。他们存在于执行线程的栈中,他们无法被其他线程访问。栈局限(也被称为线程内或者本地线程用法,不要与ThreadLocal混淆)比起ad-hoc线程局限更容易被维护并且更加强壮。
For primitively typed local variables, such as numPairs in loadTheArk in Listing 3.9, you cannot violate stack confinement even if you tried. There is no way to obtain a reference to a primitive variable, so the language semantics ensure that primitive local variables are always stack confined.
对于简单类型的本地变量,例如Listing类中的numPairs,无论如何你都不会违法栈局限。由于Java语言无法提供一个对简单类型变量的应用,因此语法本身就可以保证简单类型的本地变量永远是栈局限的。
Listing 3.9. Thread Confinement of Local Primitive and Reference Variables.
public int loadTheArk(Collection<Animal> candidates) {
SortedSet<Animal> animals;
int numPairs = 0;
Animal candidate = null;

// animals confined to method, don't let them escape!
animals = new TreeSet<Animal>(new SpeciesGenderComparator());
animals.addAll(candidates);
for (Animal a : animals) {
if (candidate == null || !candidate.isPotentialMate(a))
candidate = a;
else {
ark.load(new AnimalPair(candidate, a));
++numPairs;
candidate = null;
}
}
return numPairs;
}

Maintaining stack confinement for object references requires a little more assistance from the programmer to ensure that the referent does not escape. In loadTheArk, we instantiate a treeSet and store a reference to it in animals. At this point, there is exactly one reference to the Set, held in a local variable and therefore confined to the executing thread. However, if we were to publish a reference to the Set (or any of its internals), the confinement would be violated and the animals would escape.
要保持对象的应用的栈局限性需要程序员的更多的主动性来防止引用的逃逸。在loadTheArk方法中,我们实例化一个TreeSet对象,然后使用Animas引用该对象。此刻,只有该TreeSet只有一个应用,而且该应用是本地变量,这样栈局限就完成了。但是如果我们公开了对该Set的应用的话,局限就会被破坏,“动物”就会逃窜出来。
Using a non-thread-safe object in a within-thread context is still thread-safe. However, be careful: the design requirement that the object be confined to the executing thread, or the awareness that the confined object is not thread-safe, often exists only in the head of the developer when the code is written. If the assumption of within-thread usage is not clearly documented, future maintainers might mistakenly allow the object to escape.
将一个非线程安全的对象应用在一个单线程的上下文环境中仍然是线程安全的。但是,请注意:这种设计需求要求对象被局限在正在执行的线程中,或者注意被局限的对象并不是线程安全的,这通常只会在代码被编写的时候停留在程序员的脑海中。如果这种限制并没有被很好的文档化,那该类很可能会被后来的使用者误用。
3.3.3. ThreadLocal
A more formal means of maintaining thread confinement is ThreadLocal, which allows you to associate a per-thread value with a value-holding object. Thread-Local provides get and set accessor methods that maintain a separate copy of the value for each thread that uses it, so a get returns the most recent value passed to set from the currently executing thread.
一个更加形式良好的维护线程局限的工具是ThreadLocal,该类可以允许你将表示某个线程的值与一个保存值的对象相关联。Thread-Local提供了get和set的访问符来为每个使用他的线程来保存独立的对象拷贝,get方法可以为正在执行的线程返回使用set方法最近修改的值。
Thread-local variables are often used to prevent sharing in designs based on mutable Singletons or global variables. For example, a single-threaded application might maintain a global database connection that is initialized at startup to avoid having to pass a Connection to every method. Since JDBC connections may not be thread-safe, a multithreaded application that uses a global connection without additional coordination is not thread-safe either. By using a ThreadLocal to store the JDBC connection, as in ConnectionHolder in Listing 3.10, each thread will have its own connection.
Thread-local变量通常用来阻止基于可变的单态或者全局变量的共享。例如,单线程的应用程序可能会在初始化的时候保存一个全局的数据库连接,这样就可以避免将该连接对象传递给每一个方法。由于JDBC连接并不是线程安全的,多线程的应用程序在没有额外控制的情况下使用全局的连接就不是线程安全的。通过使用ThreadLocal类来保存JDBC连接,这样每个线程都会拥有自己的数据库连接。
Listing 3.10. Using ThreadLocal to Ensure thread Confinement.
private static ThreadLocal<Connection> connectionHolder
= new ThreadLocal<Connection>() {
public Connection initialValue() {
return DriverManager.getConnection(DB_URL);
}
};

public static Connection getConnection() {
return connectionHolder.get();
}

This technique can also be used when a frequently used operation requires a temporary object such as a buffer and wants to avoid reallocating the temporary object on each invocation. For example, before Java 5.0, Integer.toString used a ThreadLocal to store the 12-byte buffer used for formatting its result, rather than using a shared static buffer (which would require locking) or allocating a new buffer for each invocation.[11]
[11] This technique is unlikely to be a performance win unless the operation is performed very frequently or the allocation is unusually expensive. In Java 5.0, it was replaced with the more straightforward approach of allocating a new buffer for every invocation, suggesting that for something as mundane as a temporary buffer, it is not a performance win.
当需要频繁的使用一个诸如buffer这样的临时对象并且希望避免每次调用的时候都重新分配该对象的时候,这项技术同样也可以用于解决这样的问题。例如,在Java5.0之前,Integer.toString()使用ThreadLocal来保存一个12比特的buffer来格式化输出结果,而不是使用一个全局的buffer(这样做需要用到锁机制)也不是为每次调用都分配一个新的buffer。这项技术本身并不太可能会带来操作性能的提供,除非对象的分配特别频繁,或者分配的代价非常高。在Java5.0中,这种方式被一种更为直接的方法取代-为每次调用分配一个新的buffer。对于一个非常像uffer这样临时变量,这种做法并不会带来性能的提升。
When a thread calls ThreadLocal.get for the first time, initialValue is consulted to provide the initial value for that thread. Conceptually, you can think of a ThreadLocal<T> as holding a Map<Thread,T> that stores the thread-specific values, though this is not how it is actually implemented. The thread-specific values are stored in the Thread object itself; when the thread terminates, the thread-specific values can be garbage collected.
当线程第一次访问ThreadLocal.get方法的时候,initialValue方法将会为该线程提供一个初始化的值。你可以将ThreadLocal<T>想象成在其内部有一个使用Map<Thead,T>来为其保存于线程有关的值,不过这不是ThreadLocal的实际实现。线程相关的值是被保存在线程对象的内部。当线程终止的时候,线程的相关值会被垃圾回收器回收。
If you are porting a single-threaded application to a multithreaded environment, you can preserve thread safety by converting shared global variables into ThreadLocals, if the semantics of the shared globals permits this; an application wide cache would not be as useful if it were turned into a number of thread-local caches.
如果你想要将一个单线程的应用程序移植到多线程环境中,在全局共享的语义允许的情况下,你可以通过将共享的全局变量保存在ThreadLocal中的方法来维护线程安全。如果被变成多个thread-local的缓存,那么应用范围内的缓存将不再有用。
ThreadLocal is widely used in implementing application frameworks. For example, J2EE containers associate a transaction context with an executing thread for the duration of an EJB call. This is easily implemented using a static Thread-Local holding the transaction context: when framework code needs to determine what transaction is currently running, it fetches the transaction context from this ThreadLocal. This is convenient in that it reduces the need to pass execution context information into every method, but couples any code that uses this mechanism to the framework.
在应用框架的实现中,ThreadLocal被广泛的应用。例如,J2EE容器在一个EJB调用的执行期间,会将一个事物的上下文环境与一个正在执行的线程相关联。使用一个静态的ThreadLocal来保存事物的上下文环境是非常简单的:当框架代码需要判断当前正在运行的是哪个事物的时候,它只需要从ThreadLocal中取得事物的上下文环境即可。这样就避免将上下文环境传递给每一个方法,与之相伴的是框架与代码的耦合。
It is easy to abuse ThreadLocal by treating its thread confinement property as a license to use global variables or as a means of creating "hidden" method arguments. Like global variables, thread-local variables can detract from reusability and introduce hidden couplings among classes, and should therefore be used with care.
ThreadLocal类很容易被滥用。比如使用其线程局限的属性作为一个使用全局变量的依据,或者作为一个为方法创建隐藏参数的方法。与global变量一样,ThreadLocal变量会带来复用性的降低以及类之间的隐性耦合,因此应该慎用。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值