Yes, it is correct. volatile protects only that object reference, but nothing else.
No, putting an element to a volatile
HashMap will not create a happens-before relationship, not even with a ConcurrentHashMap
.
Actually ConcurrentHashMap
does not hold lock for read operations (e.g. containsKey()
). See ConcurrentHashMap Javadoc.
Update:
Reflecting your updated question: you have to synchronize on the object you put into the CHM. I recommend to use a container object instead of directly storing the Object
in the map:
public class ObjectContainer {
volatile boolean isSetupDone = false;
Object o;
}
static ConcurrentHashMap<String, ObjectContainer> containers =
new ConcurrentHashMap<String, ObjectContainer>();
public Object getInstance(String groupId) {
ObjectContainer oc = containers.get(groupId);
if (oc == null) {
// it's enough to sync on the map, don't need the whole class
synchronized(containers) {
// double-check not to overwrite the created object
if (!containers.containsKey(groupId))
oc = new ObjectContainer();
containers.put(groupId, oc);
} else {
// if another thread already created, then use that
oc = containers.get(groupId);
}
} // leave the class-level sync block
}
// here we have a valid ObjectContainer, but may not have been initialized
// same doublechecking for object initialization
if(!oc.isSetupDone) {
// now syncing on the ObjectContainer only
synchronized(oc) {
if(!oc.isSetupDone) {
oc.o = new String("typically a more complicated operation"));
oc.isSetupDone = true;
}
}
}
return oc.o;
}
Note, that at creation, at most one thread may create ObjectContainer
. But at initialization each groups may be initialized in parallel (but at most 1 thread per group).
It may also happen that Thread T1
will create the ObjectContainer, but Thread T2
will initialize it.
Yes, it is worth to keep the ConcurrentHashMap
, because the map reads and writes will happen at the same time. But volatile
is not required, since the map object itself will not change.
The sad thing is that the double-check does not always work, since the compiler may create a bytecode where it is reusing the result of containers.get(groupId)
(that's not the case with the volatile isSetupDone). That's why I had to use containsKey
for the double-checking.