我正在阅读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 主题。正如我们之前提到的,这可能是一个 利益(更新的数据)或责任(可能 根据您的要求,对船队的看法不一致。
我不明白这个缺点。为什么舰队的视野可能变得不一致。所有对象都是不可变的。
答案 0 :(得分:2)
所有对象不不可变: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
正如模糊提到的那样,这本身并不坏;这一切都取决于您的应用程序的需求和期望。