Java类库包含许多有用的“基础模块”类。一般,咱们应该优先选择重用这些现有的类而不是建立新的类:重用能下降开发工做量、开发风险(由于现有的类都已经经过测试)以及维护成本。有时候,某个现有的线程安全类能支持咱们须要的全部操做,但更多时候,现有的类智能支持大部分的操做,此时就须要在不破坏线程安全性的状况下添加一个新的操做。java
例如,假设须要一个线程安全的链表,它须要提供一个原子的“若没有则添加(putIfAbsent)”的操做。同步的List类已经实现了大部分的功能,咱们能够根据它提供的contains方法和add方法来构造一个“若没有则添加”。因为这个类必须是线程安全的,所以就隐含的增长了另外一个需求,即“若没有则添加”这个操做必须是原子操做。这意味着若是在链表中没有包含对象X,那么在执行两次“若没有则添加”X后,在容器中只能包含一个X对象。然而,若是“若没有则添加”操做不是也uanzi操做,那么在某些执行状况下,有两个线程都将看到X不在容器中,而且都执行了添加X的操做,从而使容器包含两个相同的对象。安全
要添加一个新的原子操做,最安全的方法是修改原始的类,但这一般没法作到,由于你可能没法访问或修改类的源代码。要想修改原始的类,就须要理解代码中的同步策略,这样增长的功能才能与原有的设计保持一致。若是直接将新方法添加到类中,那么意味着实现同步策略的全部代码任然处于一个源代码文件中,从而更容易理解与维护。并发
另外一种方法是扩展这个类(假定在设计这个类时考虑了可扩展性,即类不是final修饰的)。下面的代码中的BetterVector对Vector类进行了扩展,并添加了一个新方法putIfAbsent。扩展Vector很简单,但并不是全部的类都像Vector那样将状态向子类公开,所以也就不适合采用这种方法。函数
public class BetterVector<E> extends Vector<E> { public synchronized boolean putIfAbsent(E x) { boolean absent = !contains(x); if (absent) { add(x); } return absent; } }
“扩展”方法比直接将代码添加到类中更加脆弱,由于如今的同步策略实现被分不到多个单独维护的源代码文件中。若是底层的类改变了同步策略并选择了不一样的锁来保护它的状态变量,那么子类会被破坏,由于在同步策略改变后它没法再使用正确的锁来控制对基类状态的并发访问。(在Vector的规范中定义了它的同步策略,所以BetterVector不存在这个问题)性能
对于由Collections.synchronizedList封装的ArrayList,上述两种方法都行不通,由于客户端代码并不知道在同步封装器工厂中返回的List对象的类型。第三种策略是扩展类的功能,但并非扩展类自己,而是将扩展代码放入一个“辅助类”中。下面的代码实现了一个包含“若没有则添加”操做的辅助类,用于对线程安全的List进行操做看,但其中的代码是错误的:测试
public class ListHelper<E> { public List<E> list = Collections.synchronizedList(new ArrayList<E>()); public synchronized boolean putIfAbsent(E x) { boolean absent = !list.contains(x); if (absent) { list.add(x); } return absent; } }
为何这种方式不能实现线程安全性?毕竟putIfAbsent已经声明为synchronized类型的方法,对不对?问题在于在错误的锁上进行了同步。不管List使用哪个锁来保护它本身的状态,能够肯定的是,这个锁并非ListHelper上的锁。ListHelper只是带来了同步的假象,尽管全部的链表操做都被声明为synchronized,但却使用了不一样的锁,这意味着putIfAbsent相对于List的其余操做来讲并非原子的,所以就没法确保当putIfAbsent执行时,另外一个线程不会修改链表。this
要想使这个方法能正确执行,必须使List在客户端实现加锁或外部加锁时使用的是同一个锁。客户端加锁是指:对于使用某个对象X的客户端代码,使用X自己用于保护其状态的锁来保护这段客户代码。要使用客户端加锁,你必须知道对象X使用的是哪个锁。spa
在Vector和同步封装器类(Collections.synchronized[List/Map/Set])的文档中指出,它们经过使用Vector或封装容器内置锁来支持客户端加锁。线程
public class ListHelper<E> { public List<E> list = Collections.synchronizedList(new ArrayList<E>()); public boolean putIfAbsent(E x) { synchronized(list) { boolean absent = !list.contains(x); if (absent) { list.add(x); } return absent; } } }
经过添加一个原子操做来扩展类是脆弱的,由于它将类的加锁代码分布到多个类中。然而,客户端加锁却更加脆弱,由于它将类C的加锁代码放到与C彻底无关的其余类中。当在那些并不承诺遵循加锁策略的类上使用客户端加锁时,要特别当心。设计
客户端加锁机制与扩展类机制有许多共同点,两者都是将派生类的行为与积累的实现耦合在一块儿。和扩展会破坏实现的封装性同样,客户端加锁一样会破坏同步策略的封装性。
当为现有的类添加一个原子操做时,还有一种更好的方法:组合(Composition)。下面的程序中的ImprovedList经过将List对象的操做委托给底层List实力来实现,同时还添加了一个原子的putIfAbsent方法(与Collections.synchronizedList和其余容器封装器同样,ImprovedList假设把某个链表对象传给构造函数之后,客户代码不会再直接使用这个对象,而只能经过ImprovedList来访问它)。
public class ImprovedList<T> implements List<T> { private final List<T> list; public ImprovedList(List<T> list) { this.list = list; } public synchronized boolean putIfAbsent(T x) { boolean absent = !list.contains(x); if (absent) { list.add(x); } return absent; } //...按照相似的方式将ImprovedList的其余方法实现委托给List }
ImprovedList经过自身的内置锁增长了一层额外的锁。它并不关心底层的List是不是线程安全的,及时List不是线程安全的或者在其后续版本中修改了它的加锁实现,ImprovedList也会提供一直的加锁机制来实现线程安全性。虽然额外的同步层可能致使轻微的性能损失,但与模拟另外一个对象的加锁策略相比,ImprovedList更为健壮。