重用可以节省我们进行开发和测试(测试比我们自己测严谨地多)的时间和其他各种成本。
但是,对一个线程安全类进行扩展的时候就需要思考一些问题。
比如我们熟知的线程安全类Vector,该类中对所有的公有方法提供了synchronized修饰以保证访问互斥与可见性。
但Vector毕竟是一个公有的结构,他对客户代码的不变性约束一无所知。
比如客户代码中对某个Vector对象连续调用了两次方法,虽然每次都是线程安全的,但这种复合操作并不是一个原子操作,它可能不满足我们的不变性约束,于是线程安全类变得"不安全"了。
对于一种数据结构类,我们经常做put-if-absent操作。
当然,如果是在栈封闭或者是单线程应用的情况下这没什么问题。
但,如果是多线程访问同一个数据结构对象时我们就需要考虑这一操作是否是安全的?
即便我们使用的是所谓线程安全类。
于是我们会重用这个线程安全类,对其进行扩展,并保证我们的不变性约束不会受到破坏:
public class BetterVector <E> extends Vector<E> {
static final long serialVersionUID = -3963416950630760754L;
public synchronized boolean putIfAbsent(E x) {
boolean absent = !contains(x);
if (absent)
add(x);
return absent;
}
}
也许是因为我们扩展的是JDK里面的Vector,所以会给人带来一种安全感(除了有些规范中定义的同步策略,其余的情况谁都无法保证)。
但如果是你的同事提供的线程安全类呢? 谁都无法保证它不会在下一个版本时发生变化。
比较要命的是下一个版本中发生变化的偏偏是同步策略,这导致子类直接受影响。
我们需要想一个问题,如果不去继承,我们如何在重用现有的线程安全类的情况下又保证自己的不变性约束?
于是我们想到了只使用需要的方法,既然继承很危险,那我把线程安全对象作为field,把需要的功能拿过来用就可以了。
但下面是一个错误例子:
class BadListHelper <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;
}
}
不够细心的话,给人的第一反应就是:这没问题吧....
确实,我们使用的list已经用Collections.ysnchronizedList装饰过,何况我们提供的putIfAbsent也加上了synchronized关键字,这个方法确实是同步的。
但是某个线程调用putIfAbsent的时候,另一个线程也可以调用其他方法。
这样我们的不变性约束就被破坏,这个helper类变得毫无意义。
也就是说问题在于这个synchronized,我们要的不是synchronized(this),而是synchronized(list)。
因此helper应该改为:
class GoodListHelper <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;
}
}
}
到这一步已经很不错了,但是派生类和基类仍然存在一些耦合。
也许我们可以将list声明为private final,并提供一个构造器,在构造器里用Collections.ysnchronizedList进行装饰。
但即使这样仍存在行为上的耦合,我们不能针对基类的行为为其添砖加瓦。
于是我们可以使用组合来解决这一问题:
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 contains = list.contains(x);
if (contains)
list.add(x);
return !contains;
}
public int size() {
return list.size();
}
public boolean isEmpty() {
return list.isEmpty();
}
public boolean contains(Object o) {
return list.contains(o);
}
public Iterator<T> iterator() {
return list.iterator();
}
public Object[] toArray() {
return list.toArray();
}
public <T> T[] toArray(T[] a) {
return list.toArray(a);
}
public synchronized boolean add(T e) {
return list.add(e);
}
public synchronized boolean remove(Object o) {
return list.remove(o);
}
public boolean containsAll(Collection<?> c) {
return list.containsAll(c);
}
public synchronized boolean addAll(Collection<? extends T> c) {
return list.addAll(c);
}
public synchronized boolean addAll(int index, Collection<? extends T> c) {
return list.addAll(index, c);
}
public synchronized boolean removeAll(Collection<?> c) {
return list.removeAll(c);
}
public synchronized boolean retainAll(Collection<?> c) {
return list.retainAll(c);
}
public boolean equals(Object o) {
return list.equals(o);
}
public int hashCode() {
return list.hashCode();
}
public T get(int index) {
return list.get(index);
}
public T set(int index, T element) {
return list.set(index, element);
}
public void add(int index, T element) {
list.add(index, element);
}
public T remove(int index) {
return list.remove(index);
}
public int indexOf(Object o) {
return list.indexOf(o);
}
public int lastIndexOf(Object o) {
return list.lastIndexOf(o);
}
public ListIterator<T> listIterator() {
return list.listIterator();
}
public ListIterator<T> listIterator(int index) {
return list.listIterator(index);
}
public List<T> subList(int fromIndex, int toIndex) {
return list.subList(fromIndex, toIndex);
}
public synchronized void clear() { list.clear(); }
}