is Java HashSet thread-safe for read only?
If I have an instance of an HashSet after I ran it through Collections.unmodifiableSet(), is it thread-safe?
开发者_开发问答I'm asking this since Set documentation states that it's not, but I'm only performing read operations.
From the Javadoc:
Note that this implementation is not synchronized. If multiple threads access a hash set concurrently, and at least one of the threads modifies the set, it must be synchronized externally
Reading doesn't modify a set, therefore you're fine.
HashSet
will be threadsafe if used in a read-only manner. That doesn't mean that any Set that you pass to Collections.unmodifiableSet()
will be threadsafe.
Imagine this naive implementation of contains
that caches the last value checked:
Object lastKey;
boolean lastContains;
public boolean contains(Object key) {
if ( key == lastKey ) {
return lastContains;
} else {
lastKey = key;
lastContains = doContains(key);
return lastContains;
}
}
Clearly this wouldn't be threadsafe.
It would be thread safe, but only owing to the fact that Collections.unmodifiableSet()
internally publishes the target Set
in safe manner (via the final
field).
Note that in general statements such as "read-only objects are always thread-safe" are not correct, since they don't take into account possibility of operation reordering.
It's (theoretically) possible that, due to operation reordering, a reference to that read-only object will become visible to other threads before object is completely initialized and populated with data. To eliminate this possibility you need to publish references to the object in safe manner, for example, by storing them in final
fields, as it's done by Collections.unmodifiableSet()
.
Every data structure is thread-safe if you don't mutate it.
Because you have to mutate a HashSet in order to initialize it, it is necessary to synchronize once between the thread which initialized the set and all reading threads. You have to do it only one time. For example when you pass the reference to the unmodifiable set to a new thread which never touched it before.
I don't believe it is thread safe just because you run Collections.unmodifiableSet(). Even though the HashSet if fully initialized and you marked it as unmodifiable, doesn't mean that those changes will be visible to other threads. Even worse, in the absence of synchronization, a compilier is allowed to re-order instructions, which could mean that not only does a reading thread see missing data but it can also see the hashset in a wierd state. Therefore you will need some synchronization. I believe one way around this is to create the hashset as final and to fully initialize it in the constructor. Here is a good article on the JMM http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html. Read the section on How do final fields work under the new JMM?
The ability to see the correctly constructed value for the field is nice, but if the field itself is a reference, then you also want your code to see the up to date values for the object (or array) to which it points. If your field is a final field, this is also guaranteed. So, you can have a final pointer to an array and not have to worry about other threads seeing the correct values for the array reference, but incorrect values for the contents of the array. Again, by "correct" here, we mean "up to date as of the end of the object's constructor", not "the latest value available".
Yes, it is safe for concurrent read access. Here is the relevant sentence from the documentation:
If multiple threads access a hash set concurrently, and at least one of the threads modifies the set, it must be synchronized externally.
It states that you only need to synchronize if at least one
thread modifies it.
Source: https://docs.oracle.com/javase/8/docs/api/java/util/HashSet.html
If the shared memory will never be changed, you can always read without synchronizing. Making the set unmodifiable will just enforce the fact that no writes can be made.
精彩评论