1. Introduction
When dealing with Collections in Java, ConcurrentModificationException is an exception you’ll most probably encounter. In this article, you’ll learn how to fix this exception. You’ll also learn some best practices for avoiding the ConcurrentModificationException.
2. What Java Says about ConcurrentModificationException
According to the Javadoc, a ConcurrentModificationException is thrown to indicate an attempt to perform a concurrent modification of an object when such operation is not permissible. One common scenario is when a thread tries to modify a Collection while another thread is iterating over it. Another situation is when a single thread attempts to issue a sequence of method invocations that violates the contract of an object.
3. How to Reproduce
3.1. In a Multithreaded Environment
Let’s consider the following code which consists of two threads: the main
thread and the simpleThread
:
class Scratch {
public static void main(String[] args) throws Exception {
List<String> list = new ArrayList<>();
for(int i=0; i<10; i++){
list.add("hello");
}
Thread simpleThread = new Thread(new SimpleThread(list));
simpleThread.start();
for(String value: list){//ConcurrentModificationException due to ListIterator#next() method
System.out.println(value);
}
}
}
class SimpleThread implements Runnable{
private List<String> list;
public SimpleThread(List list){
this.list = list;
}
@Override
public void run() {
this.list.add("world");
}
}
Running the above program will randomly throw a ConcurrentModificationException.
hello
hello
Exception in thread "main" java.util.ConcurrentModificationException
at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1095)
at java.base/java.util.ArrayList$Itr.next(ArrayList.java:1049)
at Scratch.main(scratch_2.java:19)
Depending on the configuration of your workstation, the exception might be thrown after 2, 3, or more iterations of the for
loop. The exception might never be thrown as well. If you want to ensure that the exception is always thrown, just increase the size of list
so the loop
doesn’t complete before the simpleThread
starts.
for(int i=0; i<100; i++){//Increase here
list.add("hello");
}
The exception is thrown here because we are trying to call the
next()
method of theListIterator
on a List that has been updated after the Iterator was created.
How to Fix the Problem
A basic solution to this problem is to use an array instead of an ArrayList. This solution is only acceptable if you are dealing with small or medium size arrays as you will lose all the advantages of the Collection API.
class Scratch {
public static void main(String[] args) throws Exception {
String[] list = new String[10];
for(int i=0; i<list.length; i++){
list[i] = "hello";
}
//Adapt the remaining code accordingly
}
}
Another workaround is to use an index while looping on the list, instead of the enhanced for loop which automatically creates an Iterator.
class Scratch {
public static void main(String[] args) throws Exception {
//Remaining code is unchanged
Thread simpleThread = new Thread(new SimpleThread(list));
simpleThread.start();
for(int i=0; i < list.size(); i++){//No ConcurrentModificationException as we are not using ListIterator
System.out.println(list.get(i));
}
}
}
A more recommendable approach is to use a thread-safe implementation of ArrayList, namely CopyOnWriteArrayList
.
class Scratch {
public static void main(String[] args) throws Exception {
List<String> list = new CopyOnWriteArrayList<>();//Update here
for(int i=0; i<100; i++){
list.add("hello");
}
Thread simpleThread = new Thread(new SimpleThread(list));
simpleThread.start();
for(String value: list){//No ConcurrentModificationException
System.out.println(value);
}
}
}
When using CopyOnWriteArrayList, the Iterator is always created on a fresh copy of the List.This means that any changes made on the list will not reflect on the iterator itself.
3.2. In a Singlethreaded Environment
Let’s consider the following code snippet, where we are looping over a List of Strings.
List<String> list = new ArrayList<>();
list.add("hello");
for(String value : list){// ConcurrentModificationException
System.out.println(value);
list.add("world");
}
Any attempt to add a new element to the list within the loop body raises a ConcurrentModificationException.
Exception in thread "main" java.util.ConcurrentModificationException
at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1095)
at java.base/java.util.ArrayList$Itr.next(ArrayList.java:1049)
at Scratch.main(scratch_2.java:15)
Obviously, the same situation will happen if you try to remove an element instead of adding it. Just like we saw earlier in a multithreaded environment, the problem occurs because the ListIterator
tries to call the next()
method on a List that has been updated after the iterator was created.
How to Fix the Problem
We only focus here on solutions that use the Collection API.
Use an Index-based for
loop
As we saw earlier, an index-based for
loop does not use an Iterator under the hood.
class Scratch {
public static void main(String[] args) throws Exception {
List<String> list = new ArrayList<>();
list.add("hello");
for(int i=0; i< list.size(); i++){// No ConcurrentModificationException
System.out.println(list.get(i));
list.add("world");
}
}
}
Use a Concurrent Collection
The CopyOnWriteArrayList
is a safe alternative when you plan to update your ArrayList from different threads. You need to be aware that the list changes made after the iterator is created will not be reflected.
class Scratch {
public static void main(String[] args) throws Exception {
List<String> list = new CopyOnWriteArrayList<>();
list.add("hello");
for(String value: list){// No ConcurrentModificationException
//Any changes made by at this point will not reflect on this loop
System.out.println(value);
list.add("world");//This will not be printed
}
}
}
4. Conclusion
In this brief tutorial, you learned about the ConcurrentModificationException and how to fix it.