views:

97

answers:

3

Hi, I am working through an example in Java Concurrency in Practice and am not understanding why a concurrent-safe container is necessary in the following code.

I'm not seeing how the container "locations" 's state could be modified after construction; so since it is published through an 'unmodifiableMap' wrapper, it appears to me that an ordinary HashMap would suffice.

EG, it is accessed concurrently, but the state of the map is only accessed by readers, no writers. The value fields in the map are syncronized via delegation to the 'SafePoint' class, so while the points are mutable, the keys for the hash, and their associated values (references to SafePoint instances) in the map never change.

I think my confusion is based on what precisely the state of the collection is in the problem.

Thanks!! -Mike

Listing 4.12, Java Concurrency in Practice, (this listing available as .java here, and also in chapter form via google)

/////////////begin code

@ThreadSafe
public class PublishingVehicleTracker {

private final Map<String, SafePoint> locations;
private final Map<String, SafePoint> unmodifiableMap;

public PublishingVehicleTracker(
                        Map<String, SafePoint> locations) {
    this.locations
        = new ConcurrentHashMap<String, SafePoint>(locations);
    this.unmodifiableMap
        = Collections.unmodifiableMap(this.locations);
}

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

public SafePoint getLocation(String id) {
    return locations.get(id);
}

public void setLocation(String id, int x, int y) {
    if (!locations.containsKey(id))
        throw new IllegalArgumentException(
            "invalid vehicle name: " + id);
    locations.get(id).set(x, y);
  }
}

// monitor protected helper-class

@ThreadSafe
public class SafePoint {

@GuardedBy("this") private int x, y;

private SafePoint(int[] a) { this(a[0], a[1]); }

public SafePoint(SafePoint p) { this(p.get()); }

public SafePoint(int x, int y) {
    this.x = x;
    this.y = y;
}

public synchronized int[] get() {
    return new int[] { x, y };
}

public synchronized void set(int x, int y) {
    this.x = x;
    this.y = y;
}

}

///////////end code

+1  A: 

The method setLocation does indeed modify the contents of the map.

Update: I discussed the issue with a coworker and in the end we came to agree with @Zwei Steinen's conclusion (+1 to him :-).

Note that apart from what he mentioned, visibility is a concern too. However, in this case the map is declared final, which guarantees this.

Péter Török
yes, of course - however in this case it appears to me that we are using an ownership model where the logical state of the container is only its handles. the logical state of the items those handles point to is managed (and thus syncronized) internally to each SafePoint object.
mike
my (admittedly cursory:) knowledge of the java hash collections is that only the key is hashed, and the only thing stored for the value should be a handle to the corresponding instance.I'm primarily experienced in C++, so if I have it all wrong perhaps you could enlighten me from that perspective...Thanks!
mike
Consider the case where a hash map gets resized: the internal structure is extended and entries are remapped into new buckets. If they are not synchronized, and several threads are putting into the structure while a rehash is occurring, then there is a possibility that some entries may get unlinked and hence lost or loops introduced. Subsequent get() operations may then either return null for expected data or never return.
rhu
@mike OK, apparently we are using the same term differently; I see your point. I need to dig deeper into this myself; will get back to you if and when I have any explanation :-)
Péter Török
@rhu In general you are right; note however that in this case the hash map itself (and thus its size) is never modified, only the values stored in it.
Péter Török
@mike See my conclusion in the updated answer.
Péter Török
+2  A: 

You are right. I think it is an error in JCiP. If you want to be double sure, I suggest you post it to (its) mailing list at: http://gee.cs.oswego.edu/dl/concurrency-interest

Like you said, the map is not modified; modification of the value does not result in any "writes" on the map.

In fact, my production code does exactly what you suggest, and I have asked question on the said mailing list about that code. One of the author of JCiP told me it is okay to use a read-only hashmap for the container.

Here is the simplified version (no null check etc.) of my code (I ended up using google-collection's ImmutableMap.) :

class Sample {
    private final ImmutableMap<Long, AtomicReference<Stuff>> container;

    Sample(){
        this.container = getMap();
    }

    void setStuff(Long id, Stuff stuff){
        AtomicReference<Stuff> holder = container.get(id);
        holder.set(stuff);
    }
}

It has worked perfectly under extreme load, for an extended time.

Enno Shioji
A: 

@Zwei, thanks for the suggestion - Joe Bowbeer & Tim Peierls (JCiP coauthors) confirmed that, technically, an ordinary hash map would suffice in this case. (although any real-world design would probably have extra requirements that would necessitate the concurrent map)

The reasoning is that the underlying map is
-transitively reachable from a final field
-not changed since published via 'getLocations()' (actually its never changed after ctor, but that's not necessary)
-properly constructed

Joe pointed me to a couple very good blog posts explaining more about what java immutability really means:

http://jeremymanson.blogspot.com/2008/04/immutability-in-java.html
http://jeremymanson.blogspot.com/2008/07/immutability-in-java-part-2.html

I'd refer readers there for a complete explanation.

Thanks!

mike