java并发编程笔记day04

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 的做法可能就不正确了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值