Are final static variables thread safe in Java?

2019-01-21 22:52发布

问题:

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:

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 Guava ImmutableMap implementation 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 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.



回答2:

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.



回答3:

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.



回答4:

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.



回答5:

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, an ExceptionInInitializerError will be raised in the thread that first attempts initialization. Subsequent attempt to reference the class will raise a NoClassDefFoundError.

In general, the contents of a HashMap have no guarantee of visibility across threads. However, the class initialization code uses a synchronized block to prevent multiple threads from initializing the class. This synchronization will flush the state of the map (and the HashMap 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.



回答6:

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);
}


回答7:

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.



回答8:

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.