views:

1393

answers:

8

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.)

+1  A: 
erickson
+2  A: 

There is nothing inherently thread safe about a final static variable. Declaring a member variable final 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:

  • Do multiple threads modify the state of your sharedData variable?
  • If so, do you synchronize on all writes (and reads) of 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:

if (!map.containsKey("foo")) {
    map.put("foo", bar);
}
matt b
There *is* something inherently thread safe about a final static variable, since final means it can be assigned only once, and the assignment has to happen during class initialization, which is implicitly synchronized. Of course, that says nothing about objects referred to by such a variable, but e.g. a final static long or double is safe whereas an instance or non-final long or double is not.
Michael Borgwardt
making anything final is inherently making that variable a thread safe reference
fuzzy lollipop
A final Map is not safe to be shared among many threads without synchronization. This is what I mean by "nothing inherently thread safe about a final static variable".
matt b
@fuzzy lollipop, the reference might be thread safe, but what about the state inside the Map? That is what is key.
matt b
As long as no subsequent changes are made, that state will be synchronized: http://java.sun.com/docs/books/jls/third_edition/html/execution.html#12.4.2
erickson
@erickson, I'm not referring to the static initializer, I'm referring to what is done with the Map(s) after object construction.
matt b
I infer from the OP that no modifications are made to the map state outside of the static initializer. You'd only have a visibility problem if that's not true.
erickson
Yes, my apologizes on not giving complete context to my goals. I edited the question. My intention is that sharedData is not modified after the static initializer. I want to make the Maps that are contained in it thread-safe as well. I modified the code above to indicate that.
freshfunk
@Michael : Formally you are correct. But practically speaking the contents of a Map is not constant be default.
extraneon
A: 

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.

splix
It is not immutable. This seems to be a common misconception with final, it only prevents the reassignment of the sharedData.
Robin
@Robin - he points that out with, "any data inside it can be changed ... at any time."
erickson
You are right, it is just poor wording. Apparently I cannot undo my downvote though...sorry!
Robin
Downvote countered by gratuitous upvote. :-)
BlairHippo
sorry, english isn't my native language :( but i'm trying to fix it :)
splix
+5  A: 

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.

Mike Daniels
For increased clarity, I would recommend changing "access" to "update". If you can guarantee that all subsequent accesses are read-only through other means, then any class is thread safe (thus, some accesses are thread safe even when there is no locking or immutability). Designing for immutability makes it easier to prove the thread safety as it enforces this guarantee.
Kevin Brock
Does this include iteration?
freshfunk
@freshfunk, it is safe to iterate over a container whose contents never change. It is not safe if the contents can change.
Mike Daniels
Yeah, that's what my intuition was telling me. Mostly because the documented pitfalls around thread-safe objects were around the modification of contents during usage by other threads. But I wasn't sure if there were internal constructs (such as a current index pointer during iteration) that would not be thread-safe. For example, reading a map to index of 5 and then another thread continuing its iteration and being in the wrong spot.
freshfunk
@freshfunk, That's not actually the problem. Iterators themselves track their position within a collection. The issue is that if the contents of the collection changes during iteration, the position of an iterator is no longer meaningful. The item that it was pointing at may have shifted forward or backward, may be deleted, etc. This is why most standard Java iterators are "fail-fast". They can detect if the collection has changed since iteration was started and throw a ConcurrentModificationException instead of producing unexpected results.
Mike Daniels
+7  A: 

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 java.util.Collections.synchronizedMap() or java.util.Collections.unmodifiableMap() or use one of the Map implementations in the java.util.concurrent package. Only if you do BOTH will you have comprehensive thread safety on the Map. Any contained Maps need to be wrapped with synchronizedMap or unmodifiableMap as well, cloning by default is a shallow clone, it will just return references to container objects not complete copies. My advice is to stay away from cloneing.

fuzzy lollipop
@fuzzy lollipop - the internal state of maps (both the top-level and those enclosed within) will be flushed to main memory when the class initialization completes. Protecting from subsequent modification with an `immutableMap()` wrapper would be wise, but not necessary for visibility.
erickson
yes but it would act as documentation of the intention to future maintainer, if the map was supposed to be immutable. prefer __explicit__ over implicit always
fuzzy lollipop
@'fuzzy lollipop' - You can't just steal the Zen of Python, not even when it's true :)
extraneon
Ah. I left out a bit. The maps that are in sharedData are also final. if I did that, would that be OK for thread safeness?
freshfunk
how are the maps in the map "final"? do you mean immutable or synchronized? final doesn't make sense the way you used it.
fuzzy lollipop
sorry. updated the above code. not sure if its' implementation is exactly correct but basically the containing maps, once initialized, will not be changed. the whole structure is read-only.
freshfunk
"will not be changed" is not the same as "can't be changed", you should make them immutable with the java.util.Collections.unmodifiableMap() so they "can't" be changed for sure. Just making the variable final doesn't make the container thread safe. Only the wrapper methods will do that.
fuzzy lollipop
I see. So, in short, "final" + "unmodifiableMap()" = immutability => thread safe.
freshfunk
that still doesn't make the contained value objects in the map thread safe unless they are also immutable, your example is using String which is immutable, but say some custom object that has mutable setXXX methods would have to be syncrozined or immutable as well, as well as any objects it contained, threading is complicated and non-trival. Be sure and start marking questions as "Accepted" with the checkmark. You have a 0% accept rate right now.
fuzzy lollipop
Yes. In fact the value of the inner map is not a String. It is a Method object and yes this is getting complicated. :\ Sorry. I've only ever asked 2 questions and the last one didn't really have an answer. Thanks for your help.
freshfunk
+3  A: 

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.

extraneon
+1  A: 

No. Except if they are immutable.

The only thing they do is

  • Be class level accesible
  • Avoid the reference to be changed.

Still if your attribute is mutable then it is not thread safe.

See also: Do we synchronize instances variables which are final?

It is exactly the same except they are class level.

OscarRyz
+1  A: 

Aren't you are actually asking if the static initialization of sharedData is thread safe and only executed once?

And yes, that is the case.

Of course many people here have correctly pointed out that the contents of sharedData can still be modified.

Thirler
No. I've seen plenty of answers to that.My question is when multiple threads access sharedData's contents, whether there will be any contention.
freshfunk
@freshfunk: contention != thread-safety.
Kevin Brock