On page 65 and 66 of Java Concurrency in Practice Brian Goetz lists the following code:
@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);
unmodifiableMap = 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 name: " + id);
}
// Alternate version of getLocations (Listing 4.8)
public Map<String, Point> getLocationsAsStatic() {
return Collections.unmodifiableMap(
new HashMap<String, Point>(locations));
}
}
About this class Goetz writes:
"...the delegating version [the code above] returns an unmodifiable but 'live' view of the vehicle locations. This means that if thread A calls getLocations() and thread B later modifies the location of some of the points, those changes are reflected in the map returned to thread A."
In what sense would Thread A's unmodifiableMap be "live"? I do not see how changes made by Thread B via calls to setLocation() would be reflected in Thread A's unmodifiableMap. This would seem the case only if Thread A constructed a new instance of DelegatingVehicleTracker. But were Thread A to hold a reference to this class, I do not see how this is possible.
Goetz goes on to say that getLocationsAsStatic() could be called were an "unchanging view of the fleet required." I am confused. It seems to me that precisely the opposite is the case, that a call to getLocationsAsStatic() would indeed return the "live" view, and a call to getLocations(), were the class not constructed anew, would return the static, unchanging view of the fleet of cars.
What am I missing here in this example?
Any thoughts or perspectives are appreciated!
I think your confusion is due to misunderstanding of Collections.unmodifiableMap
. Direct mutation of the map returned by Collections.unmodifiableMap
is not allowed, however, mutating the backing map is totally fine (as long as the backing map allows mutation). For example:
Map<String,String> map = new HashMap<>();
Map<String, String> unmodifiableMap = Collections.unmodifiableMap(map);
map.put("key","value");
for (String key : unmodifiableMap.keySet()) {
System.out.println(key); // prints key
}
So, unmodifiableMap
in the DelegatingVehicleTracker
example is backed by a mutable map locations
(a thread-safe
one). setLocation
mutates locations
atomically and hence changes will be visible for threads holding references to the unmodifiableMap
knowing that those thread can't mutate the unmodifiableMap
.
Readers don't have access to locations
so mutating it will be done through DelegatingVehicleTracker
only and hence the name delegation
.