并发编程——车辆跟踪实例

217 阅读3分钟

第一个模型

需求:一个用于调度车辆的“车辆追踪器”,例如出租车、警车、货车等。

1.使用监视器模式构建一个用于调度车辆跟踪器
2.然后尝试放宽某些封装性需求同时又保持线程安全

每台车都由一个String对象来标识,并且拥有一个相应的位置坐标(x,y)。在VehicleTracker类中封装了车辆的标识和位置,因而它非常适合作为基于MVC模式的GUI应用程序中的数据模型,并且该模型将由一个视图线程和多个执行更新操作的线程共享。

视图线程

读取车辆的名字和位置,并将它们显示在界面上

Map<String, Point> locations = vehicles.getLocations();
for(String key : locations.keySet())
    renderVehicle(key, locations.get(key));

执行更新操作的线程通过从GPS设备上获取数据或者调度员从GUI界面输入数据来修改车辆位置:

void vehicleMoved(VehicleMovedEvent evt) {
    Point loc = evt.geteNewLocation();
    vehilces.setLocation(evt.getVehicleId, loc.x, loc.y);
}

车辆跟踪器

视图线程与执行更新操作的线程将并发地访问数据模型,因此该模型必须是线程安全的。下面为利用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);
    }
}

车辆坐标

MutablePoint来表示车辆的位置,它所包含的Map对象和可变Point对象都未曾发布。当需要返回车辆位置时,通过MutablePoint拷贝构造函数或deepCopy方法来复制正确的值,从而生成一个新的Map对象,并且该对象中的值与原有Map对象中的key值和value值都相同。

@NotThreadSafe
public class MutablePoint {
    public x, y;

    public MutablePoint() {x = o, y = 0;}
    public MutablePoint(MutablePoint p) {
        this.x = p.x;
        this.y = p.y;
    }
}

小结

大多数对象都是组合对象,当从头开始构建一个类,或者将多个非线程安全的类组合为一个类时,Java监视器都非常有用。

但是当所有各个组件都是线程安全的情况下还需要使用监视器模型吗?

答案是“视情况而定”

第二个模型

构造另一个版本,并发布底层的可变状态。需要修改接口以适应这种变化,即使用可变且线程安全的Point类。

SafePoint

SafePoint提供的get方法同时获得xy值,并将二者放回一个数组中返回。

  • 私有构造函数捕获模式: 若拷贝构造函数实现为this(p.x,p.y)则会产生竞态,而私有构造函数则可以避免这种竞态。
@ThreadSafe
public class SafePoint {
    @GuardedBy("this")
    private int x, y;

    private SafePoint(int[] a) { 
        this(a[0], a[1]);
    }

    public SafePoint(SafePoint) {
        this(p.get());
    }

    public SafePoint(int x, int y) {
        return new int[] {x, y};
    }

    public synchronized int[] get() {
        return new int[] {x, y};
    }

    public synchronized void set(int x, int y) {
        this.x = x;
        this.y = y;
    }
}

安全发布底层状态的车辆追踪器

@ThreadSafe
public class PulishingVehicleTracker {
    private final Map<String. SafePoint> locations;
    private final Map<String, SafePoint> unmodifiableMap;

    public PublishingVehicleTracker (Map<String, SafePoint> locations) {
        this.locations = new ConcurrentHashMap(this.locations);
        this.unmodifiableMap = Collections.unmodifiableMap(this.locations);
    }

    public Map<String, SafePoint> getLocation() {
        return unmodifiableMap;
    }

    public SafePoint getLocation(String id) {
        return locatons.get(i);
    }

    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);
    }
}

将线程安全委托给ConcurrentHashMap,只有Map中的元素是线程安全的且可变的Point,而并非不可变的。getLocation方法返回底层Map对象的一个不可变副本。调用者不能增加或删除车辆,但却可以通过修改返回Map中的SafePoint值来改变车辆位置。