【问题标题】:Publishing unmodifiable view to an internal map将不可修改的视图发布到内部地图
【发布时间】:2016-01-04 06:15:47
【问题描述】:

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

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

【问题讨论】:

    标签: java multithreading


    【解决方案1】:

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

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

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

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

    ...那么该代码的编写者可能会惊讶于它会失败。毕竟,我只是为同一辆车两次获得积分,并且没有在他们之间拨打setLocation - 那么位置怎么会改变呢?答案当然是有一些 other 线程调用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
    

    正如宣传中提到的,这本质上并不坏;这一切都取决于您的应用程序的需求和期望。

    【讨论】:

    • 你的“傻”例子其实很清楚,非常感谢。
    猜你喜欢
    • 1970-01-01
    • 2021-12-04
    • 2011-01-25
    • 2011-10-27
    • 1970-01-01
    • 2016-11-02
    • 1970-01-01
    • 2018-03-08
    • 1970-01-01
    相关资源
    最近更新 更多