How to prove that HashMap in java is not thread-safe How to prove that HashMap in java is not thread-safe multithreading multithreading

How to prove that HashMap in java is not thread-safe


This is quite easy to prove.

Shortly

A hash map is based on an array, where each item represents a bucket. As more keys are added, the buckets grow and at a certain threshold the array is recreated with a bigger size so that its buckets are spread more evenly (performance considerations). During the array recreation, the array becomes empty, which results in empty result for the caller, until the recreation completes.

Details and Proof

It means that sometimes HashMap#put() will internally call HashMap#resize() to make the underlying array bigger.

HashMap#resize() assigns the table field a new empty array with a bigger capacity and populates it with the old items. While this population happens, the underlying array doesn't contain all of the old items and calling HashMap#get() with an existing key may return null.

The following code demonstrates that. You are very likely to get the exception that will mean the HashMap is not thread safe. I chose the target key as 65 535 - this way it will be the last element in the array, thus being the last element during re-population which increases the possibility of getting null on HashMap#get() (to see why, see HashMap#put() implementation).

final Map<Integer, String> map = new HashMap<>();final Integer targetKey = 0b1111_1111_1111_1111; // 65 535final String targetValue = "v";map.put(targetKey, targetValue);new Thread(() -> {    IntStream.range(0, targetKey).forEach(key -> map.put(key, "someValue"));}).start();while (true) {    if (!targetValue.equals(map.get(targetKey))) {        throw new RuntimeException("HashMap is not thread safe.");    }}

One thread adds new keys to the map. The other thread constantly checks the targetKey is present.

If count those exceptions, I get around 200 000.


It is hard to simulate Race but looking at the OpenJDK source for put() method of HashMap:

public V put(K key, V value) {    if (key == null)        return putForNullKey(value);    //Operation 1           int hash = hash(key.hashCode());    int i = indexFor(hash, table.length);    for (Entry<K,V> e = table[i]; e != null; e = e.next) {        Object k;        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {            V oldValue = e.value;            e.value = value;            e.recordAccess(this);            return oldValue;        }    }     //Operation 2    modCount++;    //Operation 3    addEntry(hash, key, value, i);    return null;}

As you can see put() involves 3 operations which are not synchronized. And compound operations are non thread safe. So theoretically it is proven that HashMap is not thread safe.


I need to prove via unit tests that it will have problems in multithread environment.

This is going to be tremendously hard to do. Race conditions are very hard to demonstrate. You could certainly write a program which does puts and gets into a HashMap in a large number of threads but logging, volatile fields, other locks, and other timing details of your application may make it extremely hard to force your particular code to fail.


Here's a stupid little HashMap failure test case. It fails because it times out when the threads go into an infinite loop because of memory corruption of HashMap. However, it may not fail for you depending on number of cores and other architecture details.

@Test(timeout = 10000)public void runTest() throws Exception {    final Map<Integer, String> map = new HashMap<Integer, String>();    ExecutorService pool = Executors.newFixedThreadPool(10);    for (int i = 0; i < 10; i++) {        pool.submit(new Runnable() {            @Override            public void run() {                for (int i = 0; i < 10000; i++) {                    map.put(i, "wow");                }            }        });    }    pool.shutdown();    pool.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);}