线程安全性的委托
Java中对象大多是组合对象,如果组合对象都已经是线程安全的,还需要给这些组合对象增加一层线程安全机制吗?答案:“视情况而定”!
三种委托方式:
- 如果组合对象A的状态是由线程安全的对象B的状态构成的,那么A的线程安全就可以委托给B来保证。
- 如果组合对象A的状态是由线程安全的对象B、C、D…的状态构成,想让A的状态委托给这些对象,需要这些对象是彼此独立的。即不用在A包含的多个状态上额外增加不变性条件。
- 如果组合对象A下的对象有着相互制约的条件,比如B和C都是线程安全的对象但是B要大于C,这时候需要A提供自己的线程安全机制以保证原子性操作。
发布底层的状态变量
能发布的前提:
- 线程安全
- 没有任何不变性条件来约束他的值
- 对变量的操作上不存在任何不允许的状态转换
给现有的线程安全类中添加功能
- 最安全的方法修改原始的类。
- 继承这个类,但这样做很脆弱。并非所有类都像Vector装状态向子类公开,原有类一旦改变了安全策略,子类就会被破坏。
// 拓展类示例
@ThreadSafe
public class BetterVector<E> extends Vector<E>{
public synchronized boolean putIfAbsend(E x){
boolean absent = !contains(x);
if(absent)
add(x);
return absent;
}
}
- 客户端加锁,比继承类还脆弱,都会破坏同步策略的封装性。要使用某段代码需要使用这段代码本身的锁来保护,必须知道这段代码使用的是那个锁。要不然不足以支持线程安全!
//客户端加锁正确示例
@ThreadSafe
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;
}
}
}
//客户端加锁错误示例,因没有使用对象本身的锁进行保护
@NotThreadSafe
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;
}
}
- 组合,新类将原有的对象作为域,加入新的方法并用自己的锁对所有方法进行保护,使用了java的监视器模式来实现的。更为健壮,不用去管原有类是否是线程安全,都会由新类提供一致的加锁机制。
@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;
}
//按照类似的方式委托list的其他方法
public synchronized void clear(){
list.clear();
}
}
将同步策略文档化
在文档中要说明客户代码需要了解的线程安全保证,以及代码维护人员需要了解的同步策略。
了解含糊的同步策略,可以从开发者的角度去考虑。