0

If I iterate over a standard HashMap and attempt to add elements to it while iterating I get ConcurrentModificationException.

So I try an HashMap allowing concurrent adds:

ConcurrentHashMap<String, Integer> cMap = new ConcurrentHashMap<>();
cMap.put("one", 1);
cMap.forEach((key, value) -> cMap.put(key + key, value + value));
System.out.println(cMap);

However, the resulting map is a bit weird:

 {oneoneoneoneoneoneoneone=8, one=1, oneone=2, oneoneoneone=4}

and if changing the key to zx (cMap.put("zx", 1)), the result is now:

{zxzx=2, zx=1}

Questions:

1) Why this happens? The two concurrent operations (iterating and adding) should not conflict.

2) How to fix the inconsistency?

As opposite to Collections, when changing a String while iterating over the chars of that String, this issue is not being met:

        String str = scanner.next();
        for (int i = 1; i < str.length(); i++) {
            if (str.charAt(i) == str.charAt(i-1)) {
                str = str.substring(0, i-1) + str.substring(i+1);
                i = 0;
            }
        }
        if (str.length() == 0) {
            System.out.println("Empty String");
        } else {
            System.out.println (str);
        }
    }

note that in the above loop, the source String isn't actually changed but re-assigned because String is immutable and can not be modified.

The above code works fine and consistently. Is this an example of why Strings are thread safe?

  • 1
    I *knew*, I have seen that example [somewhere](https://stackoverflow.com/a/44307009/2711488)… – Holger Sep 13 '17 at 13:31
  • @Holger you have been quoted on one those comments that u made there (that caused an `OOM`) on a local Java group that we sometimes do here :) awesome to re-read that again – Eugene Sep 13 '17 at 13:47

1 Answers1

3

It comes down to which hash bucket your new elements get added. In your first example, your new elements get added to a later hash bucket than the one you're working on, and which the iterator has yet to reach. In your second example, your new elements get added to an earlier hash bucket, which the iterator has already read.

You should be very wary about modifying collections in the middle of an iteration. In your case, it might be better for you to add your new entries into a new map, and then merge them together.

Joe C
  • 15,324
  • 8
  • 38
  • 50
  • 2
    It isn't just "most likely". You can actually see the hash bucket order in the output: `one` was added first, iterator reads it, code adds `oneone` which ends up in a later bucket, iterator read it, code adds `oneoneoneone` which ends up in an even later bucket, iterator read it, code adds `oneoneoneoneoneoneoneone` which ends up in a bucket preceding `one`, so iterator ends. Default size of maps means that no rehashing occurs in these small examples. – Andreas Sep 09 '17 at 09:31