Search code examples
javamultithreadingconcurrenthashmap

What will occur if I would use non final ConcurrentHashMap


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?


Solution

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