I've read around quite a bit but haven't found a definitive answer.
I have a class that looks like this:
public class Foo() {
private static final HashMap<String, HashMap> sharedData;
private final HashMap myRefOfInnerHashMap;
static {
// time-consuming initialization of sharedData
final HashMap<String, String> innerMap = new HashMap<String, String>;
innerMap.put...
innerMap.put...
...a
sharedData.put(someKey, java.util.Collections.unmodifiableMap(innerMap));
}
public Foo(String key) {
this.myRefOfInnerHashMap = sharedData.get(key);
}
public void doSomethingUseful() {
// iterate over copy
for (Map.Entry<String, String> entry : this.myRefOfInnerHashMap.entrySet()) {
...
}
}
}
And I'm wondering if it is thread safe to access sharedData from instances of Foo (as is shown in the constructor and in doSomethingUseful()). Many instances of Foo will be created in a multi-threaded environment.
My intention is that sharedData is initialized in the static initializer and not modified thereafter (read-only).
What I've read is that immutable objects are inherently thread safe. But I've only seen this in what seems to be the context of instance variables. Are immutable static variables thread safe?
The other construct I found was a ConcurrentHashMap. I could make sharedData of type ConcurrentHashMap but do the HashMaps it contains also have to be of type ConcurrentHashMap? Basically..
private static final ConcurrentHashMap<String, HashMap> sharedData;
or
private static final ConcurrentHashMap<String, ConcurrentHashMap> sharedData;
Or would it be safer (yet more costly to simply clone())?
this.myCopyOfData = sharedData.get(key).clone();
TIA.
(Static initializer has been edited to give more context.)
What is thread-safe? Sure, the initialization of the HashMap is thread-safe in the respect that all Foo's share the same Map instance, and that the Map is guaranteed to be there unless an exception occurs in the static init.
But modifying the contents of the Map is most assuredly not thread safe. Static final means that the Map sharedData can not be switched for another Map. But the contents of the Map is a different question. If a given key is used more than once at the same time you may get concurrency issues.
At this case only sharedData object is immmutable, that means only that all the time you will work with same object. But any data inside it can be changed (removed, added, etc) at any time, from any thread.
There is nothing inherently thread safe about a
final static
variable. Declaring a member variablefinal static
only ensures that this variable is assigned to just once.The question of thread safety has less to do with how you declare the variables but instead relies on how you interact with the variables. So, it's not really possible to answer your question without more details on your program:
sharedData
variable?sharedData
?Using a ConcurrentHashMap only guarantees that the individual methods of the
Map
are thread-safe, it doesn't make an operation such as this thread-safe:Yes, this is thread safe too. All final members of your static class will be initialized before any thread is allowed to access them.
If the
static
block fails during initialization, anExceptionInInitializerError
will be raised in the thread that first attempts initialization. Subsequent attempt to reference the class will raise aNoClassDefFoundError
.In general, the contents of a
HashMap
have no guarantee of visibility across threads. However, the class initialization code uses asynchronized
block to prevent multiple threads from initializing the class. This synchronization will flush the state of the map (and theHashMap
instances that it contains) so that they will be correctly visible to all threads—assuming that no changes are made to the map, or the maps it contains, outside the class initializer.See the Java Language Specification, §12.4.2 for information about class initialization and the requirement for synchronization.
Initialization of static final fields in a static initialization block is thread safe. However, remember that the object to which a static final reference points may not be thread safe. If the object to which you refer is thread safe (e.g., it's immutable), you're in the clear.
Each individual HashMap contained in your outer HashMap is not guaranteed to be thread safe unless you use ConcurrentHashMap as suggested in your question. If you do not use a thread-safe inner HashMap implementation, you may get unintended results when two threads access the same inner HashMap. Keep in mind that only some operations on ConcurrentHashMap are synchronized. For example, iteration is not thread-safe.
the reference to
sharedData
which is final is thread safe since it can never be changed. The contents of the Map is NOT thread safe because it needs to be either wrapped with preferably a GuavaImmutableMap
implementation orjava.util.Collections.unmodifiableMap()
or use one of the Map implementations in thejava.util.concurrent
package.Only if you do BOTH will you have comprehensive thread safety on the Map. Any contained Maps need to be immutable or one of the concurrent implementations as well.
.clone() is fundamentally broken, stay away
cloning by default is a shallow clone, it will just return references to container objects not complete copies. It is well documented in generally available information on why.