Search⌘ K
AI Features

ConcurrentModificationException

Explore the causes and scenarios of ConcurrentModificationException in Java. Understand how concurrent modifications during iteration trigger this exception in both single-threaded and multithreaded contexts. Learn the role of different map types, including why ConcurrentHashMap is designed for safe concurrent use. Gain insight into handling data consistency and exceptions in multithreaded Java programs.

If you are interviewing, consider buying our number#1 course for Java Multithreading Interviews.

Single Thread Environment

The name ConcurrentModificationException may sound related to concurrency, however, the exception can be thrown while a single thread operates on a map. In fact, ConcurrentModificationException isn’t even part of the java.util.concurrent package. The exception occurs when a map is modified at the same time (concurrently) any of its collection views (keys, values or entry pairs) is being traversed. The program below demonstrates the exception being thrown as the main thread traverses the map entries and also attempts to insert new entries.

Java
import java.util.*;
class Demonstration {
public static void main( String args[] ) {
HashMap<String, Integer> map = new HashMap<>();
// Fill the HashMap with some data
int i = 0;
for (i = 0; i < 100; i++) {
map.put("key-" + i, i);
}
// Get an iterator for the entries in the map
Iterator it = map.entrySet().iterator();
while (it.hasNext()) {
// Add a new key/value pair while the map is
// being traversed.
map.put("key-" + i, i);
it.next();
i++;
}
}
}

Multithread Environment

In case of a single threaded environment it is often trivial to diagnose ConcurrentModificationException cause, however, in multithreaded scenarios, it may be difficult to do so as the exception may occur intermittently depending on how threads are scheduled for execution. Concurrent modification occurs when one thread is iterating over a map while another thread attempts to modify the map at the same time. A usual sequence of events is as follows:

  1. Thread A obtains an iterator for the keys, values or entry set of a map.

  2. Thread A begins to iterate in a loop.

  3. Thread B comes along and attempts to delete, insert or update a key/value pair in the map.

  4. ConcurrentModificationException is thrown when thread A attempts to retrieve the next item in the collection it is iterating.

Since the map has been modified from the time the iterator for the map was created, the thread iterating over the collection can observe inconsistent data and a ConcurrentModificationException is thrown. The program below demonstrates interaction between two threads that results in the exception.

Java
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
class Demonstration {
public static void main( String args[] ) throws Exception {
HashMap<String, Integer> map = new HashMap<>();
ExecutorService es = Executors.newFixedThreadPool(5);
try {
// create a task that slowly reads from the map.
Runnable reader = new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException ie) { /*ignore*/ }
for (Map.Entry<String, Integer> entry : map.entrySet()) {
try {
Thread.sleep(1000);
} catch (InterruptedException ie) { /*ignore*/ }
System.out.println("key " + entry.getKey() + " value " + entry.getValue());
}
}
};
// create a task to write to the map a little faster than the reader
Runnable writer = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(10);
} catch (InterruptedException ie) { /*ignore*/ }
map.put("key-" + i, i);
}
}
};
// submit the task twice
Future future1 = es.submit(writer);
Future future2 = es.submit(reader);
// wait for the threads to finish
future1.get();
future2.get();
} finally {
// We know the code will generate a ConcurrentModificationException so
// remember to shutdown the executor service in a finally block
es.shutdown();
}
}
}

It is not only the HashMap that suffers from ConcurrentModificationException, other maps exhibit same behavior. The only map that is designed to be concurrently modified while being traversed is the ConcurrentHashMap. The program below demonstrates the behavior of all the maps.

Java
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
class Demonstration {
public static void main( String args[] ) {
test(new Hashtable<String, Integer>());
test(new HashMap<String, Integer>());
test(Collections.synchronizedMap(new HashMap<String, Integer>()));
test(new ConcurrentHashMap<String, Integer>());
}
static void test(Map<String, Integer> map) {
// Put some data in the map
int i;
for (i = 0; i < 10; i++) {
map.put("key-" + i, i);
}
Iterator it = map.entrySet().iterator();
while (it.hasNext()) {
map.put("key-" + i, i);
try {
it.next();
} catch (ConcurrentModificationException ex) {
System.out.println("ConcurrentModificationException thrown for map " + map.getClass().getName());
return;
}
i++;
}
System.out.println("No exception thrown for map " + map.getClass().getName());
}
}

Even though the ConcurrentHashaMap can undergo concurrent modifications (additions, deletions, updates) at the same time as its elements are being traversed, the modifications may not be reflected during the traversal. Consider the program below in which a reader thread starts traversing a map’s entries while the map is being written to by a writer thread. The reader thread only observes a limited number of entries reflecting the state of the map at some point at or since the creation of the iterator/enumeration.

Java
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
class Demonstration {
public static void main( String args[] ) throws Exception {
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
ExecutorService es = Executors.newFixedThreadPool(5);
try {
// create a reader that slowly reads from the map.
Runnable reader = new Runnable() {
@Override
public void run() {
// wait for writer to put in some entries in the map
try {
Thread.sleep(20);
} catch (InterruptedException ie) { /*ignore*/ }
int seen = 0;
for (Map.Entry<String, Integer> entry : map.entrySet()) {
try {
Thread.sleep(10);
} catch (InterruptedException ie) { /*ignore*/ }
entry.getValue();
seen++;
}
System.out.println("Number of entries seen by reader thread : " + seen);
}
};
// create a writer that inputs 1000 entries in the map
Runnable writer = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
try {
Thread.sleep(5);
} catch (InterruptedException ie) { /*ignore*/ }
map.put("key-" + i, i);
}
System.out.println("Writer thread finished.");
}
};
// submit the two tasks
Future future1 = es.submit(writer);
Future future2 = es.submit(reader);
// wait for the threads to finish
future1.get();
future2.get();
} finally {
es.shutdown();
}
}
}

In the above program, the writer inserts 1000 entries into the map but the reader only sees a handful.

As a user of ConcurrentHashMap one has to be cognizant of the limitation of iterators/enumerators, which may return a snapshot of the map taken at the time of creation of the iterator/enumeration or later.

Technical Quiz
1.

Consider the program below and answer:

static void quiz() {

    Map<String, Integer> map = new HashMap<>();
    Random random = new Random(System.currentTimeMillis());

    // Put some data in the map
    for (int i = 0; i < 10; i++) {
        map.put("key-" + i, i);
    }

    Iterator it = map.entrySet().iterator();

    while (it.hasNext()) {
        it.next();
        int k = random.nextInt(10);
        map.put("key-" + k, k);
    }
}
A.

Program throws ConcurrentModificationException

B.

Program doesn’t throw any exception


1 / 1

The above scenario is very interesting since we are modifying the map but we are essentially overwriting the same key/value pair and no exception is thrown. In some cases such as handling duplicates (e.g. key/value pairs received from a message bus), a program can overwrite the same key/value pair twice (an example of idempotent write) and continue to function correctly but under different conditions may throw ConcurrentModificationException.

The program from the quiz is reproduced in the widget below.

Java
import java.util.*;
class Demonstration {
public static void main( String args[] ) {
Map<String, Integer> map = new HashMap<>();
Random random = new Random(System.currentTimeMillis());
// Put some data in the map
for (int i = 0; i < 10; i++) {
map.put("key-" + i, i);
}
Iterator it = map.entrySet().iterator();
while (it.hasNext()) {
it.next();
int k = random.nextInt(10);
map.put("key-" + k, k);
}
System.out.println("Program completes successfully.");
}
}