组合对象
探讨一些构造类的模式,使得类更容易成为线程安全的。
设计线程安全的类
设计线程安全的类的过程应该包含三个方面:
- 确定对象状态是由哪些变量构成–变量;
- 确定限制对象状态的不变约束–不变约束;
- 制定一个管理并发访问对象状态的策略–后验条件。
不变约束:用来判定一个状态是合法的还是不合法的,比如int的取值范围,是施加在状态上的约束;
后验条件:指出某种状态转变是否合法,是施加在状态操作上的约束;
上述二者需要引入额外的同步和封装。
组合:将一个对象封装到另一个对象内部。
组合使得被封装对象的全部访问路径都是可知的,这相比让整个应用系统 访问对象来说,更容易对访问路径进行分析,然后再和各种适当的锁相结合,可以确保程序能以线程安全的方式使用其它非线程安全的对象。
在并发领域,组合是为保证线程安全的的一个线程限制。
Java监视器模式
一种线程限制原则,遵循该原则的对象封装了所有的可变状态,并使用对象的内部锁来保护。例如:
public class PrivateLock {
private final Object myLock = new Object();
@GuardedBy("myLock") Widget widget;
void someMethod() {
synchronized (myLock) {
// Access or modify the state of widget
}
}
}
委托线程安全
一些由线程安全的组件组合而成的组件未必是线程安全的。比如这些线程安全的子组件有依赖性。见代码:
public class NumberRange {
// INVARIANT: lower <= upper
private final AtomicInteger lower = new AtomicInteger(0);
private final AtomicInteger upper = new AtomicInteger(0);
public void setLower(int i) {
// Warning -- unsafe check-then-act
if (i > upper.get())
throw new IllegalArgumentException("can't set lower to " + i + " > upper");
lower.set(i);
}
public void setUpper(int i) {
// Warning -- unsafe check-then-act
if (i < lower.get())
throw new IllegalArgumentException("can't set upper to " + i + " < lower");
upper.set(i);
}
public boolean isInRange(int i) {
return (i >= lower.get() && i <= upper.get());
}
}
若有两个线程,同时,分别访问setLower和setUpper,则可能出现线程安全问题。
只有当一个类有多个彼此独立的 线程安全的状态变量组合而成,且类的操作不包含任何无效的状态转换时,才可以将线程安全委托给这些状态变量。
向已有的线程安全类中添加功能
重用Java自带的线程安全类,要好于创建一个新的,无论在难度,风险或是维护上。比如对一个List添加功能:缺少则添加。即先判断list中是否有此元素,无,则添加。此时涉及到了检查-执行这一复合操作,按照之前同步策略,是可以在此操作上加锁将其变成原子性操作的,但是因为源码我们没法修改,只能找别的方式。这里,组合,或是继承都可以。比如继承:
@ThreadSafe
public class BetterVector <E> extends Vector<E> {
// When extending a serializable class, you should redefine serialVersionUID
static final long serialVersionUID = -3963416950630760754L;
public synchronized boolean putIfAbsent(E x) {
boolean absent = !contains(x);
if (absent)
add(x);
return absent;
}
}
不过组合(这里只指原功能的线程安全委托给子组件)的话,需要一些其它的操作,不能直接在方法上同步。因为Q1操作无法保证helper类封装的list的其它方法和putif..的同步问题。
@NotThreadSafe//Q1操作,不安全
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;
}
...
}
@ThreadSafe
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;
}
}
...
}
还有另一种组合方式,就是将所组合的对象中所有存在风险的方法都加上内部锁,而不用依赖子组件对象是否线程安全。
@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 x) {
boolean contains = list.contains(x);
if (contains)
list.add(x);
return !contains;
}
public synchronized boolean add(T e) {
return list.add(e);
}
public synchronized boolean remove(Object o) {
return list.remove(o);
}
.....other alike methods..
写好文档,非常重要!
//待下篇