4.3 委托线程安全
4.3.1 范例:试用委托的机动车追踪器
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 setLocaltion(String id, int x, int y ){
if (locations.replace(id, new Point(x, y)) == null ) {
throw new IllegalArgumentException("invilad vehicle name :"+id);
}
}
}
public class Point {
public final int x,y;
public Point(int x,int y){
this.x = x;
this.y = y;
}
}
我们在这里对VehicleTracker 类进行了略微的改变:基于监听器的代码返回localtion的快照,基于委托的代码则返回一个不可变的,但是现场的localtion视图。
如果需要一个不可变的瞬时视图,getLocaltion可以返回一个localtions的Map的灰拷贝,只复制对象的引用,因此复制的对象与原始的对象是同一个对象,相反深拷贝复制的是对象的所有成员变量,因此复制的对象与原始的对象不是一个对象。因为Map的内容是不变的,因此需要复制的只有map的结构而不包括它的内容,如下代码所示:
public Map<String, Point> getLocaltions(){
return Collections.unmodifiableMap(
new HashMap<String,Point>(locations));
}
4.3.2 非状态依赖变量
目前为止,我们在委托实例中仅仅委托了一个单一的 线程安全的状态变量。我们也可以将线程安全委托到多个隐含的变量上去,只要这些变量彼此都是独立的,这意味着组合对象并未增加任何涉及多个状态变量的不变约束。
委托线程安全到多个底层的状态变量:
public class VisualComponent {
private final List<KeyListener> keyListeners =new CopyOnWriteArrayList<KeyListener>();
private final List<MouseListener> mouseListeners = new CopyOnWriteArrayList<MouseListener>();
public void addKeyListener(KeyListener listener){
keyListeners.add(listener);
}
public void addMouseListener(MouseListener listener){
mouseListeners.add(listener);
}
public void removeKeyListener(KeyListener listener){
keyListeners.remove(listener);
}
public void removeMouseListener(MouseListener listener){
mouseListeners.remove(listener);
}
}
在VisualComponent 类中,我们的每个list都是线程安全的,而且不存在哪个不变约束会增加一个状态与另一个状态间的耦合,所以VisualComponent 可以将它的线程安全委托到keyListeners 和mouseListeners 上。
4.3.3 当委托无法胜任时
如果像下面代码使用两个AtomicInteger管理NumberRange的状态,而且受到一个额外的约束条件–第一个数小于或等于第二个数。
NumberRange 是线程不安全的,因为它没有保护好用于约束lower和uper的不变约束。setUpper和setLower试图保护不变约束,但是明显办不到。setUpper和setLower都是先检查后运行的操作,但是它们没有适当的加锁以保证其原子性。
假设初始为(0,10) 当一个线程调用setlower(6) 另一个线程调用setupper(4)此时并发执行,先检查的条件都是满足的,那么执行之后的数据为(6,4)一个无效的状态。这个例子很明显的告诉我们虽然AtomicInteger 是线程安全的,但是组合起来就不一定,因为lower和upper不是彼此独立的。
public class NumberRange {
private final AtomicInteger lower = new AtomicInteger(0);
private final AtomicInteger upper = new AtomicInteger(0);
public void setLower(int i) {
if (i > lower.get()) {
throw new IllegalArgumentException("cannot set lower to " + i + ">lower");
}
lower.set(i);
}
public void setUpper(int i) {
if (i < upper.get()) {
throw new IllegalArgumentException("cannot set upper to " + i + ">upperr");
}
upper.set(i);
}
public boolean isInRange(int i) {
return (i >= lower.get() && i <= upper.get());
}
}
4.3.4 发布底层的状态变量
如果一个类是线程安全的,没有任何不变约束限制它的值,并且没有任何状态转换限制它的操作,那么它可以被安全的发布。
举个例子 ,大栗子 !QUQ 我们上面的VisualComponent 发布mouseListeners 或者
keyListeners 是安全的,因为VisualComponent 没有对监听器清单的合法状态进行限制,所以把这些域声明为public,或者发布它们,都不会危及到线程的安全。
4.3.5 示例:发布了状态的机动车追踪器
又是一个大栗子。。。啦啦啦。。。
我们构造一个发布了底层状态的版本的机动车追踪器,我们要改变一下接口适应,这次试用可变但线程安全的Point,我们这里的Point提供了一个get函数获得一个包含x,y的二位数组。因为我们如果单独为x,y提供get方法的话那么我们获得的x,y的值可以是在两个坐标上的x,y会导致此时取到的x,y位置上并没有我们的机动车。我们使用SafePoint来构建一个机动车追踪器,它发布了底层的可变状态,缺不破话线程的安全性。
@ThreadSafe
public class SafePoint {
@GuardedBy("this")
private int x,y;
private SafePoint(int[] a){
this(a[0],a[1]);
}
public SafePoint(SafePoint p){
this(p.get());
}
public SafePoint(int x,int y){
this.x= x;
this.y=y;
}
public synchronized int[] get(){
return new int[]{x,y};
}
public synchronized void set(int x,int y){
this.x=x;
this.y=y;
}
}
public class PublishingVehicleTracker {
private final Map<String, SafePoint> locations;
private final Map<String, SafePoint> unmodifiableMap;
public PublishingVehicleTracker(Map<String, SafePoint> locations){
this.locations = new ConcurrentHashMap<String,SafePoint>(locations);
this.unmodifiableMap = Collections.unmodifiableMap(this.locations);
}
public Map<String, SafePoint> getLocations(){
return unmodifiableMap;
}
public SafePoint getLocation(String id){
return unmodifiableMap.get(id);
}
public void setLocation(String id ,int x, int y){
if (!locations.containsKey(id)) {
throw new IllegalArgumentException("invalid vehicle name :"+id);
}
locations.get(id).set(x, y);
}
}
PublishingVehicleTracker 的线程安全源于它所委托的底层ConcurrentHashMap,不过这次的map的内容是线程安全的可变Point,而非不变的。getlocation返回底层map的不可变拷贝,调用者在其上无法添加或移除车辆,却可以修改返回的map中的safepoint的值来达到修改一辆机动车的位置。map的这一现场特性是否有价值,还是一个缺陷,任然取决于需求。只有PublishingVehicleTracker 对机动车追踪器的合法值没有施加任何额外的约束时,它才是现场安全的。如果我们对location的改变进行判断或者执行一些其他操作,那么PublishingVehicleTracker 的做法可能就不正确了