8

I've been trying to prove there's a bug in the application by creating a simple unit test which is putting values to the map. I was expecting ConcurrentModificationException, but all I got was hanging threads in the executor and I don't see where exactly is the problem.

The test is here:

@Test
public void testHashMap() throws Exception {
    final Random rnd = new Random();
    final Map<String, Object> map = new HashMap<>();
    ExecutorService executor = Executors.newFixedThreadPool(10);
    for (int i = 0; i < 100; i++) {
        final int counter=i;
        executor.execute(new Runnable() {
            @Override
            public void run() {
                try{
                    for (int j = 0; j<1000; j++){
                        map.put(String.valueOf(rnd.nextLong()), new Object());
                        //map.put("A", new Object());
                    }
                    System.out.println("Thread "+counter+" finished");
                }catch(Exception e){
                    System.out.println("Thread "+counter+" failed with exception: ");
                    e.printStackTrace();
                }
            }
        });
    }
    executor.shutdown();
    int i = 0;
    while (!executor.isTerminated()) {
        i++;
        Thread.sleep(1000);
        System.out.println("Waited "+i+" seconds");
    }
}

I know that I shouldn't do this, but I don't understand why am I not getting exception and why the threads just hang there? When I do a simple put on the map(the commented code), then it passes fine.

Here is the sample output:

Thread 0 finished
Thread 1 finished
Thread 4 finished
Thread 2 finished
Thread 5 finished
Thread 7 finished
Thread 9 finished
Thread 10 finished
Thread 13 finished
Thread 6 finished
Thread 14 finished
Thread 8 finished
Thread 12 finished
Thread 16 finished
Thread 19 finished
Thread 20 finished
Thread 21 finished
Thread 26 finished
Thread 25 finished
Thread 24 finished
Thread 28 finished
Thread 3 finished
Thread 31 finished
Thread 30 finished
Thread 32 finished
Thread 34 finished
Thread 35 finished
Thread 36 finished
Thread 37 finished
Thread 38 finished
Thread 39 finished
Thread 22 finished
Thread 27 finished
Thread 42 finished
Thread 43 finished
Thread 41 finished
Thread 45 finished
Thread 44 finished
Thread 47 finished
Thread 48 finished
Thread 49 finished
Waited 1 seconds
Waited 2 seconds
Waited 3 seconds
Waited 4 seconds
Waited 5 seconds
...indefinitely
NeplatnyUdaj
  • 6,052
  • 6
  • 43
  • 76
  • 1
    You cannot use a `HashMap` without external synchronization. You should switch to use `ConcurrentHashMap`. – Gray Apr 08 '14 at 18:10
  • I know I shouldn't use that and why, but I don't understand why I'm observing the behavior from the test. – NeplatnyUdaj Apr 08 '14 at 18:19
  • Multithreaded puts into a `HashMap` could result in infinite loops if the linked list chains get put into a corrupted state where they accidentally become cyclical linked lists. – Louis Wasserman Apr 08 '14 at 18:21
  • @NeplatnyUdaj I can't reproduce the hanging behaviour - what version of Java are you using? The behaviour of HashMap in such a context is obviously implementation dependent and may change across versions since there is no guarantee as to what result you will get. – assylias Apr 08 '14 at 18:22
  • 1
    Yes HashMap can livelock (endless loop) when concurrently putting things. You normally see that if you take two threaddumps in short sequence and compare the threads, one would be in the same code area. – eckes Apr 08 '14 at 18:25
  • assylias: I'm using Java 1.7.0_25 64bit on Linux – NeplatnyUdaj Apr 08 '14 at 18:26
  • 1
    Related to: http://stackoverflow.com/questions/17070184/hashmap-stuck-on-get/17070215#17070215 – Gray Apr 08 '14 at 19:01

3 Answers3

9

Why does the code hang with HashMap.put() from multiple threads?

You cannot use a HashMap with multiple threads without external synchronization. You should switch to use ConcurrentHashMap.

You could also use the Collections.synchronizedMap(new HashMap<>()); but the ConcurrentHashMap should give better performance.

I was expecting ConcurrentModificationException, but all I got was hanging threads in the executor and I don't see where exactly is the problem.

You are seeing a hang because one of the threads has a corrupted version of the HashMap -- probably a looped linked list where two hash entries are linked to each other or something. If you do a thread dump you will see that it is spinning while traversing the HashMap entries.

The ConcurrentModificationException is only thrown if the HashMap class detects a modification. Typically this is in single-threaded usage when you (for example) remove an entry by calling map.remove(...) instead of iterator.remove() while iterating across the map.

Synchronization does two important things: mutex locking and memory synchronization. Each processor has its own memory cache and threads can easily see partially synchronized views of an object (your HashMap in this case) without proper memory synchronization.

The only thing which is quite frustrating is that sometimes it throws an exception, but most of the time it just hangs.

In multithreaded situations, there are tons of race conditions by definition since the threads are usually running in parallel on multiple processors. It's very hard to predict, given the parallel nature of the environment, on the type of failure.

Gray
  • 115,027
  • 24
  • 293
  • 354
  • This looks like a good explanation. The only thing which is quite frustrating is that sometimes it throws an exception, but most of the time it just hangs. It was quite unimportant part of the code and I didn't really do it right and just surrounded it with try-catch. Now I see it can lead to infinite loop. Never saw that coming... – NeplatnyUdaj Apr 08 '14 at 18:31
  • In multithreaded situations, there are tons of race conditions. It's _very_ hard to predict, given the parallel nature of the code, on the type of failure @NeplatnyUdaj. – Gray Apr 08 '14 at 18:32
3

Quote from the docs for HashMap:

Note that the fail-fast behavior of an iterator cannot be guaranteed as it is, generally speaking, impossible to make any hard guarantees in the presence of unsynchronized concurrent modification. Fail-fast iterators throw ConcurrentModificationException on a best-effort basis. Therefore, it would be wrong to write a program that depended on this exception for its correctness: the fail-fast behavior of iterators should be used only to detect bugs.

It goes on to recommend:

Map m = Collections.synchronizedMap(new HashMap(...));

To ensure that your map is synchronized.

http://docs.oracle.com/javase/7/docs/api/java/util/HashMap.html#put(K, V)

Matthew Wilson
  • 2,045
  • 14
  • 27
2

HashMap is not thread-safe. The problem you see is exactly what I have seen happen in production apps a few times (unfortunately...). What happens is that HashMap.put needs to make changes to the internal data structures. You may see it logically as one operation: putting an object into the map. But under the hood, it may need to do various house-keeping like re-sizing the internal hash table.

This needs to be done atomically. The put method may need to read data and then update it based on what it finds. If two threads do it at the same time, they are stepping on each other's feet. Imagine if one of those reads involves a loop, such as looping over the objects in a hash table bucket. If one thread loops over it while the table is being re-arranged by another thread, it can end up in an infinite loop.

Long-story short: Use a synchronized block or Hashtable.

Brandon
  • 9,822
  • 3
  • 27
  • 37