将不可修改的视图发布到内部地图

时间:2016-01-04 06:15:48

标签: java multithreading

我正在阅读B. Goetz Java Concurrency In Practice,现在我正处于委托线程安全的部分。他提供了以下例子:

@Immutable
public class Point{
    public final int x, y;

    public Point(int x, int y){
        this.x = x;
        this.y = y;
    }
}

@ThreadSafe
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);
        unomdifiableMap = Collections.unmodifiableMap(locations);
    }

    public Map<String, Point> getLocations(){
        return unmodifiableMap;
    }

    public Point getLocation(String id){
        return locations.get(id);
    }

    public void setLocation(String id, int x, int y){
        if(locations.replace(id, new Point(x, y)) == null)
             throw new IllegalArgumentException("invalid vehicle id: " + id);
    }

}

他说那个

  

如果线程 A 调用getLocations并且线程 B 稍后修改   某些点的位置,这些变化都反映在了   Map返回到 A 主题。正如我们之前提到的,这可能是一个   利益(更新的数据)或责任(可能   根据您的要求,对船队的看法不一致。

我不明白这个缺点。为什么舰队的视野可能变得不一致。所有对象都是不可变的。

1 个答案:

答案 0 :(得分:2)

所有对象不可变:locations不是,unmodifiableMap也不是。

问题可能不像你想要的那么棘手。由于locations是线程安全的,并且unmodifiableMaplocations的(不可变)引用之外没有任何状态,因此没有奇怪的内存可见性问题。

奇怪的是,对于这个类的消费者,getLocation看起来可以“神奇地”改变任何给定线程的值。换句话说,如果一个线程这样做:

Point p1 = tracker.getLocation("vehicle1");
Point p2 = tracker.getLocation("vehicle1");
assert p1.equals(p2);

...那么该代码的作者可能会对它失败感到惊讶。毕竟,我只是为同一辆车获得了两次,并没有在他们之间拨打setLocation - 所以位置怎么可能改变了?答案当然是一些名为setLocation其他线程,我看到两次调用getLocation之间发生了变化。

上面的例子显然有点傻,但不那么愚蠢的例子也不难想象。例如,假设您的应用程序想要对车队进行快照,并假设两辆卡车不能同时处于同一点。这在物理世界中是一个合理的假设,但它不是您的应用程序可以做出的,因为一辆卡车可能在调用getLocation之间移动到另一辆卡车的位置:

Thread1 (taking a snapshot)             Thread2 (updating locations)
                                        setLocation("truckA", 10, 10);
                                        setLocation("truckB", 20, 20);
p1 = getLocation("truckA") // (10, 10)
                                        setLocation("truckA", 5, 10);
                                        setLocation("truckB", 10, 10);
p2 = getLocation("truckB") // (10, 10)
assert !p1.equals(p2);     // fails

正如模糊提到的那样,这本身并不坏;这一切都取决于您的应用程序的需求和期望。