java并发编程笔记day04

来源:互联网 发布:销售数据周报模板xls 编辑:程序博客网 时间:2024/06/06 18:42

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来构建一个机动车追踪器,它发布了底层的可变状态,缺不破话线程的安全性。

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