Avoid ConcurrentModificationException while looping over Java ArrayList?

Apart from the NullPointerException and ClassNotFoundException, ConcurrentModificationException is another nightmare for Java developers. What makes this error tricky is the word concurrent, which always mislead Java programmers that this exception is coming because multiple threads are trying to modify the collection at the same time. Then begins the hunting, they spent countless hours to find the code which has the probability of concurrent modification. While in reality ConcurrentModficationException can also come on the single threaded environment. To give you an example, just loop over a list using for loop and try to remove one element, you will get the ConcurrentModificatoinExcetpion? Why? because you broke the rule of not modifying a Collection during iteration.


How does Java knows to throw ConcurrentModificationExeption? It uses a transient variable called modCount, which keeps track of how many times a list is modified structurally. Structural modifications are those that change the size of the list, which may affect the progress of iteration and may yield incorrect results. Both Iterator and ListIterator uses this field to detect unexpected change. Other methods of List which structurally modify List also uses this method e.g. add(), remove().


Problem: loop over an ArrayList and remove selected elements, but remove() is throwing "Exception in thread "main" java.util.ConcurrentModificationException".


Cause: The real cause of ConcurrentModfiicationException is inconsistent modCount. When you are iterating over ArrayList then Iterator's next() method keep track of modCount. If you modify the collection by adding or removing element then modCount will change and it will not match with the expected modCount, hence Iterator will throw ConcurrentModificationException.

Here is the code snippet from hasNext() method which shows there is check for modCount:

public E next() {
   checkForComodification();
   int i = cursor;
   if (i >= size)
        throw new NoSuchElementException();
    Object[] elementData = ArrayList.this.elementData;
    if (i >= elementData.length)
        throw new ConcurrentModificationException();
    cursor = i + 1;
    return (E) elementData[lastRet = i];
 }

Now if you check this checkForComodification() method, you will find what I just said:

final void checkForComodification() {
    if (modCount != expectedModCount)
      throw new ConcurrentModificationException();
}


Solution: Use Iterator if you are doing it on the single threaded environment, otherwise use concurrent collection classes e.g. CopyOnWriteArrayList to remove elements while you are looping over it.


Solving ConcurrentModificationException in ArrayList

Here is the Java program to demonstrate one scenario where you get the ConcurrentModificationException even if just one thread is modifying the ArrayList. In this example, we are looping over ArrayList using advanced for loop and removing selected elements, but because we are using ArrayList's remove() method.


import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

/**
 * Java Program to demonstrate how to deal with
 * ConcurrentModificationException.
 * Unlike the name suggests, this error can come even if only 
 * one thread is modifying the collection e.g. List. 
 * It happens when you modify collection 
 * while iterating over it e.g. adding new element or removing elements.
 * 
 * If you want to remove elements while traversing list then 
 * make sure you use Iterator's remove() method or not ArrayList's remove()
 * method() to avoid ConcurrentModificationExcetpion.
 * 
 * @author WINDOWS 8
 *
 */
public class ConcurrentModExceptionDemo{

    public static void main(String args[]) {

        List<String> listOfPhones = new ArrayList<String>(Arrays.asList(
                "iPhone 6S", "iPhone 6", "iPhone 5", "Samsung Galaxy 4", "Lumia Nokia"));
        
        System.out.println("list of phones: " + listOfPhones);
        
        // Iterating and removing objects from list
        // This is wrong way, will throw ConcurrentModificationException
        for(String phone : listOfPhones){
            if(phone.startsWith("iPhone")){
               // listOfPhones.remove(phone); // will throw exception
            }
        }
        
        // The Right way, iterating elements using Iterator's remove() method
        
        for(Iterator<String> itr = listOfPhones.iterator(); itr.hasNext();){            
            String phone = itr.next();            
            if(phone.startsWith("iPhone")){
                // listOfPhones.remove(phone);  // wrong again
                itr.remove(); // right call
            }
        }
        
        System.out.println("list after removal: " + listOfPhones);

    }

}

Output :
list of phones: [iPhone 6S, iPhone 6, iPhone 5, Samsung Galaxy 4, Lumia Nokia]
list after removal: [Samsung Galaxy 4, Lumia Nokia]

If you uncomment the commented code in first loop and second loop, you will get the following exception:

Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(Unknown Source)
at java.util.ArrayList$Itr.next(Unknown Source)
at dto.ReverseArrayInPlace.main(ReverseArrayInPlace.java:28)

because we are using ArrayList's remove() method. In the second example, we have used remove() method of Iterator and that's why we are successfully able to delete selected elements from the ArrayList without ConcurrentModificationException.

Here is summary of important points about solving ConcurrentModificationException while looping over ArrayList in Java :

How to solve ConcurrentModificationException in Java


That's all about how to deal with ConcurrentModificationException in Java. The biggest thing to learn and remember is that this error can come even if you have just one thread modifying collection e.g. removing elements while looping over the list.


Related troubleshooting guides
Here are some handy Java tips to solve some common error and exception in Java:

  • How to deal with java.lang.NoClassDefFoundError: org/apache/xmlbeans/XmlObject? (solution)
  • How to solve "could not create the Java virtual machine" error in Java? (solution)
  • Fixing java.lang.unsupportedclassversionerror unsupported major.minor version 50.0 (solution)
  • java.lang.ClassNotFoundException: org.apache.commons.logging.LogFactory error (solution)
  • How to solve java.lang.ClassNotFoundException: com.mysql.jdbc.Driver error? (hint)
  • How to solve java.lang.classnotfoundexception oracle.jdbc.driver.oracledriver? (solution)
  • java.lang.ClassNotFoundException : org.Springframework.Web.Context.ContextLoaderListener (solution)
  • How to fix 'javac' is not recognized as an internal or external command (solution)
  • How to fix Caused By: java.lang.NoClassDefFoundError: org/apache/log4j/Logger (solution)
  • How to solve java.lang.OutOfMemoryError: Java Heap Space in Eclipse, Tomcat? (solution)


Further Reading
If you are looking to improve your skills on Java Collection framework then I suggest to go over Java Generics and Collection book, the best book to get in-depth knowledge of Collections.


No comments:

Post a Comment