How does DelegatingVehicleTracker (p. 65 Goetz) return a "live" view?
Asked Answered
H

3

6

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!

Haematin answered 4/11, 2015 at 21:51 Comment(0)
I
4

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.

Insight answered 4/11, 2015 at 22:30 Comment(2)
Thanks Sleiman, and you others as well; now I get it. I'll spell it out to myself (of use hopefully to others): DelegatingVehicleTracker can modify, but client classes cannot. A call to unmodifiableMap in your example code above would throw an exception (I tried it out), but, of course, the insert to map does not. UnmodifiableMap is like a puppet show seen through a glass screen which bars the audience from meddling.Haematin
@BenWeaver there is a term for the metaphor you just described, it is delegationInsight
T
1

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 is because getLocations() returns an unmodifiable wrapped map of the actual mutable map.

public DelegatingVehicleTracker(Map<String, Point> points) {
    locations = new ConcurrentHashMap<String, Point>(points);
    unmodifiableMap = Collections.unmodifiableMap(locations);
}
...

public Map<String, Point> getLocations() {
    return unmodifiableMap;
}

So any changes later will be automatically reflected in the original returned map since they both eventually point to the same internal Map object.

Goetz goes on to say that getLocationsAsStatic() could be called were an "unchanging view of the fleet required"

This code

public Map<String, Point> getLocationsAsStatic() {
return Collections.unmodifiableMap(
        new HashMap<String, Point>(locations));
}

is static in the sense that future changes to locations are not reflected since it returns a new map with a copy of the all the current key-value pairs.

Transmissible answered 4/11, 2015 at 21:58 Comment(0)
C
1

getLocations() will return a read-only map which will reflect updates after getLocations() is called.

getLocationsAsStatic() on the other hand, will return a read-only-snapshot (aka deep copy) of the Location map at the time getLocationsAsStatic() is called.

To illustrate:

Map<String, Point> locs     = // a map with point A(1,1)  in it
DelegatingVehicleTracker tracker = DelegatingVehicleTracker(locs);

Map<String, Point> snapshot = getLocationsAsStatic();
Map<String, Point> live     = getLocations();

Point newB = // a point A(2,2)
tracker.setLocation(newB);

snapshot.get("A"); // will read A(1,1)
live.get("A");      // will read A(2,2)
Cerda answered 4/11, 2015 at 23:34 Comment(1)
thats actually the question, it is what the OP doesn't understandInsight

© 2022 - 2024 — McMap. All rights reserved.