我正在阅读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的那样,这并不是天生的坏事。这完全取决于您的应用程序的需求和期望。