对象的组合

本章将介绍一些组合模式,将一些现有的线程安全组件组合为更大规模的组件或程序,并且在维护这些类时,不会无意中破坏线程安全性保证。

设计线程安全的类

设计线程安全的类需要考虑以下三个基本要素:

  • 找出构成对象状态的所有变量
  • 找出约束状态变量的不变性条件
  • 建立对象状态的并发访问管理策略

收集同步需求

依赖状态的操作

状态的所有权

实例封闭

当一个对象被封装到另一个对象时,能够访问被封装对象的所有代码路径都是已知的,更易于对代码分析。

将数据封装在对象内部,可以将数据的访问限制在对象的方法上,从而更容易确保线程在访问数据时总能持有正确的锁。

1
2
3
4
5
6
7
8
9
10
11
@ThreadSafe
public class PersonSet {
@GuardBy("this")
private final Set<Person> mySet = new HashSet();
public synchronized void addPerson(Person p) {
mySet.add(p);
}
public synchronized boolean containsPerson(Person p) {
return mySet.contains(p);
}
}

JDK类库中有很多线程封闭的示例,其中有些类的唯一用途就是将非线程安全的类转化为线程安全的类,如ArrayList并非线程安全的,但类库提供了装饰器工厂方法,如Collections.synchronizedList,使得他们能在多线程环境中安全使用。

Java监视器模式

遵循Java监视器模式的对象会把对象所有的可变状态封装起来,并由自己的内置锁来保护,JDK中许多类都使用了Java监视器模式,如Vector、HashTable。

Java监视器模式的主要优势在于其简单性。

线程安全性的委托

独立的状态变量

我们可以将线程安全性委托给多个状态变量,只要这些变量是彼此独立的,即组合而成的类并不会在其包含的多个状态变量上增加任何不变性条件。

当委托失效时

举了一个NumberRange使用AtomicInteger类来保存数字范围的上限与下限,却并不能保证线程安全性的例子。因为状态变量upper/lower不是彼此独立的。

如果一个类由多个独立且线程安全的状态变量组成,并且所有的操作中都不包含无效状态装换,那么可以将线程安全性委托给底层的状态变量。

发布底层的状态变量

如果一个状态变量是线程安全的,并且没有任何不变性约束来约束它的值,在变量操作上也不存在任何不允许的状态转换,那么就可以安全地发布这个变量。

在现有的线程安全类中添加功能

修改原始类代码通常无法做到。

继承原始类并扩展方法可以做到,但比直接将代码添加到类中更加脆弱,因为同步策略被分布到多个单独维护的源代码中,如果底层的类改变了同步策略并选择了不同的锁来保护它的状态变量,那么子类的线程安全性将会被破坏。

客户端加锁机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@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;
}
}
}

客户端加锁将类List的加锁代码放到了与之无关的ListHelper类中,同样会破坏同步策略的封装性。

组合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@ThreadSafe
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;
}
public synchronized void clear() {
list.clear();
}
// ... 按照类似的方法委托List接口的其他方法
}

ImprovedList通过内置锁增加了一层额外的加锁,它不必关心底层的list是否线程安全,ImprovedList提供了一致的加锁机制来实现线程安全性。
虽然额外的同步层可能带来轻微的性能损失,但其线程安全性更为健壮。

将同步策略文档化