设计线程安全的类

设计线程安全的类,需要包含以下三个基本要素。

找出构成对象状态的所有变量。

要分析对象的状态,首先从对象的域开始,如果对象的所有域都是基本类型,那么这些域将构成对象的全部状态。

找出约束状态变量的不变性条件。

变量的不变性条件是指变量的约束条件,比如希望变量a是正整数,那么在使用类时,就不得创建出负数的变量a。
再比如a表示数值的上界,b表示数值的下界,要保证a>b,a和b就必须在单个原子操作中进行读取或更新。

建立对象状态的并发访问管理策略。

实例封闭(注意与线程封闭区分)

将实例封装在对象的内部即为实例封闭。

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

不管Person是不是线程安全的类,都可以将它封闭到PersonSet,但是要使用锁确保线程线程安全。

java类库中,ArrayList和HashMap等非线程安全的类,正是通过线程封闭实现的包装器(例如Collections.synchronizedList等),才可以在多线程中安全使用。

将线程安全性委托给底层的状态变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Immutable
public class Point{
public int x, y;
public Point(int x, int y){
this.x = x;
this.y = y;
}
}
@ThreadSafe
public class DelegatingVehicleTracker{
private final ConcurrentMap<String,Point> locations;
private final Map<String,Point> unmodifiableMap;
public DelegatingVehicleTracker(Map<String,Point> points){
locations = new ConcurrentHashMap<String,Point>(points);
unmodifiableMap = Collections.unmodifiableMap(locations);
}
public Map<String,Point> getLocations(){
return unmodifiableMap;
}
public Point getLocation(String id){
return locations.get(id);
}
public void setLocation(String id, int x, int y){
if(locations.replace(id, new Point(x, y)) == null){
throw new IllegalArgumentException("invalid vehicle name: "+ id);
}
}
}

DelegatingVehicleTracker用于追踪汽车的位置,它将线程安全性委托给了内部的ConcurrentHashMap。

方法getLocations返回locations的不可修改的副本,不允许进行增删等操作。如果Point是可变的,那么仍然可以通过读操作获取Point对其进行修改,所幸这里Point是不变的。

unmodifiableMap始终与原始Map保持一致,所以getLocations始终返回最新的车辆位置信息。如果需要一个不发生变化的车辆视图,可以返回原始map的浅拷贝。

1
2
3
public Map<String,Point> getLocations(){
return Collections.unmodifiableMap(new HashMap<String,Point>(locations));
}

这里的状态变量只有一个,当线程安全性委托给多个状态变量时,就需要检查是否存在约束状态变量的不变性条件,如果多个状态变量互相独立,不存在约束条件,就可以将线程安全性委托给底层的状态变量

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

要在现有的类中添加功能,最安全的方式是修改现有的类,然而通常没有修改源码的权限。

扩展
1
2
3
4
5
6
7
8
9
10
//扩展Vector并增加一个“若没有则添加”方法
@ThreadSafe
public class BetterVector<E> extends Vector<E>{
public synchronized boolean putIfAbsent(E x){
boolean absent = !contains(x);
if(absent)
add(x);
return absent;
}
}

这种方式比较脆弱,它依赖底层类的同步策略,一旦底层的选择了不同的锁来保护它的状态变量,子类就会被破坏。

客户端加锁
1
2
3
4
5
6
7
8
9
10
11
12
13
@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;
}
}
}

使用客户端加锁需要注意,这里要为 list动态添加putIfAbsent方法,所以putIfAbsent应该持有list的锁,切勿使用客户端ListHelper的锁。

这种实现方式也比较脆弱,它将类的加锁代码分布在各个客户端类中,不利于维护。

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

ImprovedList通过内置锁保证线程安全,这里并不关心List的同步策略,我们只需要维护好ImprovedList即可。

推荐使用这种方式,尽管额外的同步会导致轻微的性能损失,然而它更加健壮。