Search code examples
javaconcurrencydictionary

Java Concurrency: Are "get(Key) for HashMap and ConcurrentHashMap equal in performance?


Are get(Key) method calls for a standard HashMap and a ConcurrentHashMap equal in performance when no modifications happen for the underlying Map (so only get() operations are performed.)

Update with Background:

Concurrency is quite a complex topic: I do need "concurrency/threadsafety" but only on puts, that happen extremely seldom. And for the puts I could swap the Map Associations itself (which is atomic and threadsafe).

Therefore I am asking I am doing a lot of gets (and have the option to either implement it with a HashMap (create a temporary Hashmap, Copy Data into a new HashMap, and swap association) or using a ConcurrentHashMap... As my App really does a lot of gets I want to learn more how performance is lost with both different gets.

As silly this sounds, the internet has so much unnecessary information around but this is something I think could be of interest to a lot more people. So if someone knows the inner workings of ConcurrentHashMap for gets it would be great to answer the question.

Thanks very much!


Solution

  • You could look at the source code. (I'm looking at JDK 6) HashMap.get() is pretty simple:

    public V get(Object key) {
            if (key == null)
                return getForNullKey();
            int hash = hash(key.hashCode());
            for (Entry<K,V> e = table[indexFor(hash, table.length)];
                 e != null;
                 e = e.next) {
                Object k;
                if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
                    return e.value;
            }
            return null;
        }
    

    Where hash() does some extra shifting and XORing to "improve" your hash code.

    ConcurrentHashMap.get() is a bit more complex, but not a lot

    public V get(Object key) {
        int hash = hash(key.hashCode());
        return segmentFor(hash).get(key, hash);
    }
    

    Again, hash() does some shifting and XORing. setMentFor(int hash) does a simple array lookup. The only complex stuff is in Segment.get(). But even that doesn't look like rocket science:

    V get(Object key, int hash) {
       if (count != 0) { // read-volatile
          HashEntry<K,V> e = getFirst(hash);
          while (e != null) {
             if (e.hash == hash && key.equals(e.key)) {
                V v = e.value;
                if (v != null)
                   return v;
                return readValueUnderLock(e); // recheck
              }
              e = e.next;
          }
     }
      return null;
    }
    

    The one place where is gets a lock is readValueUnderLock(). The comments say that this is technically legal under the memory model but never known to occur.

    Overall, looks like the code is pretty similar for both. Just a bit better organized in ConcurrentHashMap. So I'd guess that the performance is similar enough.

    That said, if puts really are extremely rare, you could consider implementing a "copy on write" type of mechanism.