4.4向已有的线程安全类添加功能
- 缺少即添加操作(检查再运行)这种操作必须是原子的
- 添加一个新原子的最安全的方式是 修改原始的类以支持期望的操作。
- 另一个方法是扩展这个类。 如下代码所示:
@TreadSafe
public class BetterVector<E> extends Vector<E> {
public synchronized boolean putIfAbsent(E e){
boolean absent = !contains(e);
if (absent) {
add(e);
}
return absent;
}
}
4.4.1 客户端加锁
对于一个由Collections.synchronizedList 封装的ArrayList两种方法都不正确,因为客户端代码不知道同步封装工厂方法返回的list对象的类型,第三个方法是扩展功能,而不是扩展类本身,并将扩展功能的代码写入到一个Helper类中。
- 非线程安全的Helper类:
这里的问题在于同步行为发生在错误的锁上,无论lis使用哪个锁保护它的状态,可以确定这个锁没有用到ListHelper上,listHelper仅仅描绘了同步的幻象:即使一个list的操作全部声明为synchronized,但是使用了不同的锁,将意味着putAbsent对于List的其他操作而言,并不是原子化,所以当putAbsent执行时,不能保证另一个线程不会修改list。
public class ListHelper<E> {
public List<E> list = Collections.synchronizedList(new ArrayList<E>());
public synchronized boolean putIfAbsent (E e){
boolean absent = !list.contains(e);
if (absent) {
list.add(e);
}
return absent;
}
}
- 线程安全的Helper类
我们必须要保证方法使用的锁和list用于客户端加锁与外部加锁时使用的锁是用一个锁,客户端加锁必须保证客户端代码与对象X保护自己状态时使的是相同锁。
其实客户端加锁是很脆弱的,因为它必须要将一个类中的加锁代码置于与它完全无关的类中。
@ThreadSafe
public class ListHelper<E> {
public List<E> list = Collections.synchronizedList(new ArrayList<E>());
public boolean putIfAbsent (E e){
synchronized(list){
boolean absent = !list.contains(e);
if (absent) {
list.add(e);
}
return absent;
}
}
}
- 客户端加锁和扩展类很多相同:所得类的行为和基类的实现之间存在耦合。扩展会破坏实现的封装统一性,客户端加锁会破坏同步策略的封装性。
4.4.2 组合
向已有的类中添加一个原子操作,还有更健壮的选择:组合。
如下代码所示ImprovedList通过将操作委托给底层的list实例,实现了list的操作,同时还增加了一个putIfAbsent方法
通过内部锁,ImprovedList引入了一个新的锁层,它并不关心底层的list是否线程安全,即使list是线程不安全的,或者会改变ImprovedList锁的实现,ImprovedList都有自己兼容的锁可以来提供线程安全性,当然这会带来一些额外的性能损失。只要我们的类持有底层List的唯一外部引用,那么就能保证线程安全性。
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 t){
boolean absent = !list.contains(t);
if (absent) {
list.add(t);
}
return absent;
}
// ***other methods implements List***
}
4.5 同步策略的文档化
在维护线程安全性的过程中,文档时最强大的工具之一。用户确定一个类是否线程安全的,会查阅文档;维护者为了理解实现策略以避免在维护中无意威胁到线程安全性也会查阅文档。