views:

211

answers:

3

Hello Experts,

is

final Map<Integer,Map<String,Integer>> status = new ConcurrentHashMap<Integer, Map<String,Integer>>();
Map<Integer,Map<String,Integer>> statusInner = new ConcurrentHashMap<Integer, Map<String,Integer>>();
status.put(key,statusInner);

the same as

volatile Map<Integer,Map<String,Integer>> status = new ConcurrentHashMap<Integer,   Map<String,Integer>>();
Map<Integer,Map<String,Integer>> statusInner = new ConcurrentHashMap<Integer, Map<String,Integer>>();
status.put(key,statusInner);

in case the inner Map is accessed by different Threads?

or is even something like this required:

volatile Map<Integer,Map<String,Integer>> status = new ConcurrentHashMap<Integer, Map<String,Integer>>();
volatile Map<Integer,Map<String,Integer>> statusInner = new ConcurrentHashMap<Integer, Map<String,Integer>>();
status.put(key,statusInner);

In case the it is NOT a "cascaded" map, final and volatile have in the end the same effect of making shure that all threads see always the correct contents of the Map... But what happens if the Map iteself contains a map, as in the example... How do I make shure that the inner Map is correctly "Memory barriered"?

Tanks! Tom

+1  A: 

I think the best answer here is that volatile is not a way to ensure thread-safety.

Using ConcurrentHashMap is pretty much all you need. Yes, make the reference to the top-level Map final if you can, but volatile is not necessary in any event. The second-level Map reference inside are the business of ConcurrentHashMap to get right, and one assumes it does.

Sean Owen
Assuming things is a pretty good way to introduce a bug.
jasonmp85
You might also want to expose that the second level map must be concurrent, if you are exposing the top-level map rather than encapsulating them (the definitions above allow insertion of any Map implementation into the top level; if you are assuming thread safety of the second level you should specify a thread safe map type).
Pete Kirkham
"Assuming things is a pretty good way to introduce a bug": Assuming the javadoc of a widely-used JDK class, whose very point is the behavior being assumed, inherited from Doug Lea's pretty well battle-tested concurrency package, strikes you as a probable source of a bug?
Sean Owen
I didn't see any Javadoc citations, quotes, or links. If you have the semantics of `ConcurrentHashMap` memorized, then sure. I'm not saying that your statements concerning `ConcurrentHashMap` are incorrect or that the poster shouldn't use it. I'm saying he/she should consult the Javadocs and make sure his/her assumptions re: read/write ordering semantics of `ConcurrentHashMap` are correct, especially if the poster is unfamiliar with the classes.
jasonmp85
+1  A: 

volatile only affects the ability of other threads to read the value of the variables it's attached to. It in no way affects the ability of another thread to see the keys and values of the map. For instance, I could have a volatile int[]. If I change the reference—i.e. if I change the actual array that it points to—other threads reading the array are guaranteed to see that change. However, if I change the third element of the array no such guarantees are made.

If status is final, the construction of the containing class creates a happens-before relationship with any subsequent reads, so they are able to see the value of status. Likewise any reads to your volatile variable are guaranteed to see the latest reference assignment to it. It's unlike you're swapping the actual map around very often, more like you're just changing keys and the overall map object stays as is.

For this question, then, we need to consult the documentation for ConcurrentHashMap:

Retrieval operations (including get) generally do not block, so may overlap with update operations (including put and remove). Retrievals reflect the results of the most recently completed update operations holding upon their onset.

This is kind of oddly worded, but the gist is that any get operation whose onset is after some put operation's return is guaranteed to see the results of that put. So you don't even need a volatile on the outer map; quoth the JLS:

A thread that can only see a reference to an object after that object has been completely initialized is guaranteed to see the correctly initialized values for that object's final fields.

Summary

A final on the outer map is sufficient.

jasonmp85
Hello jasonmp85, thanks for the helpful answer. Just to make shure and double check my understanding: Your example with the int[] Array is very interesting and good. In case I would only change the ArrayElements from different threads (not the array instance itself), I am forced to "somehow synchronize" the access to the array elements to achieve the "happens before" relationship. (Most likely I would be synchronizing on the index like synchronized(index) { myintarray[index]=foo; } ? I hope this is correct!? Thank you very much
Tom
The JLS is the authoritative source on this (http://java.sun.com/docs/books/jls/third_edition/html/memory.html), but yes, you'd have to *force* some sort of synchronization between two threads sharing array elements, otherwise one thread may not see writes of the other. One option is to reassign the array reference after any update: `volArray[5] = 10; volArray=volArray;`. While odd, this self-assignment is a volatile write—a synchronizes-with action—so any read from this array in another thread after the self-assignment will be guaranteed to see the new element (happens-before is transitive).
jasonmp85
However! Don't do that. It's fragile and someone will change it without realizing what they're breaking. Use something like this class (http://java.sun.com/javase/6/docs/api/java/util/concurrent/atomic/AtomicIntegerArray.html) if you need volatile semantics on array elements.
jasonmp85
+1  A: 

It's worth looking at Google-Collections and, in particular, MapMaker that lets you intelligently setup and create Maps. Being able to setup weak values, to enable better garbage collection, and expiration times, so you can use Maps for effective caching, is brilliant. As the Maps that MapMaker makes (:p) have the same properties as ConcurrentHashMap, you can be happy with its thread-safety.

final mapMaker = new MapMaker().weakValues(); //for convenience, assign
final Map<Integer,Map<String,Integer>> status = mapMaker.makeMap();
status.put(key, mapMaker.<String, Integer>makeMap());

Please note, you might want to look at your definition of statusInner, as it doesn't seem right.

Ben Smith
+1 for pointing out MapMaker, which I hadn't seen yet. I always wondered where I could find a concurrent weak hash map!
jasonmp85