我正在阅读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。如前所述,这可以是一个
  利益(最新数据)或负债(可能
  机队视图不一致),具体取决于您的要求。


我不明白缺点。为什么机队的观点可能会变得不一致。所有对象都是不可变的。

最佳答案

所有对象都不是不变的:locations不是,因此unmodifiableMap也不是。

该问题可能比您要查找的要难一些。由于locations是线程安全的,并且unmodifiableMap除了对locations的(不可变)引用以外没有其他状态,因此不存在奇怪的内存可见性问题。

奇怪的是,对于此类的使用者,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


正如众说纷blur的那样,这并不是天生的坏事。这完全取决于您的应用程序的需求和期望。

08-06 19:58