当对象下一个状态需要依赖当前状态时,这个操作必须是一个复合操作。并非所有操作都会在状态转换上施加限制,比如直接更新字段,以赋值形式,典型的就是javabean的get/set方法。
如果在一个不变性条件中包含多个变量,那么在执行任何访问相关变量的操作时候,都必须持有保护这些变量的锁。
下面的PersonSet说明了如何通过实例封闭与加锁机制使一个可变对象或者线程不安全对象成为一个线程安全对象:
@ThreadSafe
public class PersonSet {
@GuardedBy("this")
private final Set<Person> mySet = new HashSet<Person>();
public synchronized void addPerson(Person p) {
mySet.add(p);
}
public synchronized boolean containsPerson(Person p) {
return mySet.contains(p);
}
interface Person {
}
}
HashSet本身并不是线程安全的,但是由于它是一个私有成员变量,唯一可以访问mySet代码路径就是公共方法addPerson和containsPerson,但是这个两个方法都加了锁,所以会同步,因此是线程安全的。
一个基于监视器模式的车辆追踪示例:
@NotThreadSafe
public class MutablePoint {
public int x, y;
public MutablePoint() {
x = 0;
y = 0;
}
public MutablePoint(MutablePoint p) {
this.x = p.x;
this.y = p.y;
}
}
MonitorVehicleTracker.java:
@ThreadSafe
public class MonitorVehicleTracker {
@GuardedBy("this")
private final Map<String, MutablePoint> locations;
public MonitorVehicleTracker(Map<String, MutablePoint> locations) {
this.locations = deepCopy(locations);
}
public synchronized Map<String, MutablePoint> getLocations() {
return deepCopy(locations);
}
public synchronized MutablePoint getLocation(String id) {
MutablePoint loc = locations.get(id);
return loc == null ? null : new MutablePoint(loc);
}
public synchronized void setLocation(String id, int x, int y) {
MutablePoint loc = locations.get(id);
if (loc == null)
throw new IllegalArgumentException("No such ID: " + id);
loc.x = x;
loc.y = y;
}
private static Map<String, MutablePoint> deepCopy(Map<String, MutablePoint> m) {
Map<String, MutablePoint> result = new HashMap<String, MutablePoint>();
for (String id : m.keySet())
result.put(id, new MutablePoint(m.get(id)));
return Collections.unmodifiableMap(result);
}
}
大多数组合对象都会存在:它们的状态变量之间存在某些不变形条件。比如下面的数值范围类NumberRange.java,最小值必须≤最大值:
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());
}
}
如果某个类含有复合操作,例如NumberRange,那么仅靠委托并不足以实现线程安全性。也就是说,即便是每个实例域都是线程安全的,但是复合操作却不一定是线程安全的。
如果一个类是由多个独立且线程安全的状态变量组成,并且在所有操作中都不包含无效状态转换,那么可以将线程安全性委托给底层的状态变量。 注意上面的无效状态转换,比如一个long类型的变量,它的值的改变有个约束就是不能大于Long.MAX值。
本人博客已搬家,新地址为:http://yidao620c.github.io/