I read somewhere that even if ConcurrentHashMap
is guaranteed to be safe for using in multiple threads it should be declared as final
, even private final
. My questions are the following:
1) Will CocurrentHashMap
still keep thread safety without declaring it as final
?
2) The same question about private keyword. Probably it's better to ask more general question - do public/private
keywords affect on runtime behavior? I understand their meaning in terms of visibility/usage in internal/external classes but what about meaning in the context of multithreading runtime? I believe code like public ConcurrentHashMap
may be incorrect only in coding style terms not in runtime, am I right?
It might be helpful to give a more concrete example of what I was talking about in the comments. Let's say I do something like this:
public class CHMHolder {
private /*non-final*/ CHMHolder instance;
public static CHMHolder getInstance() {
if (instance == null) {
instance = new CHMHolder();
}
return instance;
}
private ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
public ConcurrentHashMap<String, String> getMap() {
return map;
}
}
Now, this is not thread-safe for a whole bunch of reasons! But let's say that threadA sees a null
value for instance
and thus instantiates the CHMHolder
, and then threadB, by a happy coincidence, sees that same CHMHolder
instance (which is not guaranteed, since there's no synchronization). You would think that threadB sees a non-null
CHMHolder.map
, right? It might not, since there's no formal happens-before edge between threadA's map = new ...
and threadB's return map
.
What this means in practice is that something like CHMHolder.getInstance().getMap().isEmpty()
could throw a NullPointerException
, which would be confusing — after all, getInstance
looks like it should always return a non-null
CHMHolder
, and CHMHolder
looks like it should always have a non-null
map. Ah, the joys of multithreading!
If map
were marked final
, then the JLS bit that user2864740 referenced applies. That means that if threadB sees the same instance that threadA sees (which, again, it might not), then it'll also see the map = new...
action that threadA
did -- that is, it will see the non-null
CHM instance. Once it sees that, CHM's internal thread safety will be enough to ensure safe access.