Wednesday, April 18, 2012

How to Fix ConcurrentModificationException?

I was recently writing a java program to find dependency jars for a given Jar. The approach was pretty simple. I had two HashMaps.
One to store the final list of jar dependencies and the other to store the visited Jar names. This way I could keep track of my recursive loop and wouldn't loose my way if there were any circular dependencies. A little background, the jar in question had about 500 dependency jars and many had cyclic dependencies. Here is my initial code which threw the ConcurrentModificationException.
import java.util.HashMap;
....

public class FindJarDependencies {
   String pathToInitialJar;

  // Mapping from "name of jar" -> "path of jar"
  Map dependencies = new HashMap();
  // Mapping from "name of jar" -> "True|false" ( True, if jar has been visited otherwise  false)
  Map visitedJars = new HashMap();
   
  public List getDependenciesForJar(String pathToInitialJar) {
    List result = ArrayList();
    Iterator iter = dependencies.valueSet().iterator();
    while (iter.hasNext()) {
      result.add(iter.next());
    }
    return result;
  }

  /**
   * In view of having a short blog, 
   * lets assume this method adds the manifest-classpath 
   * for a given jar into the dependency Map. 
   */
  private void addJarDependencies(String pathOfJar) {
    // Open the jar file and add manifest-classpath to dependencies Map.
  }

  public void walkDependencyJar(String pathOfJarDependency) {
     // Strip name from path of jar
     String jarName = getNameFromPath(pathOfJarDependency);

     // add jar if not present in dependency Map.
     if (!dependency.get(jarName)) 
       addJarDependencies(pathOfJarDependency);


    // Add dependency Map values to visitedJars. 
    // By default the value would be false.    
    for (String s : dependencies.keySet()) {
      if (!visitedJar.containsKey(s)) {
        visitedJar.put(s,false);
      }
    }
    
    // Iterate through the visitedJars and add them to the dependency as and when 
    // you find new jars.
    Iterator iter = visitedJars.keySet().iterator();
    while(iter.hasNext()) {
      String jarClasspath = iter.next();
      if (!visitedJar.get(jarClasspath)) {
        visitedJar.put(jarClasspath, true);         // Oops ConcurrentModificationException
        walkDependencyJar();
      }          
    }   
  }
}

When I ran this program all hell broke loose on line number 54. On further debugging I figured out the problem.
In my code I was iterating over "visitedJar" at line 51 but inside the same loop at line number 54 I am trying to change the Map. This was the root cause. Java Iterator doesn't allow for changes to iterating data structure. For better understanding I wrote a stripped down version which throws the same error.

package com.example.ConcurrentModificationExample;

import java.util.*;

public class ConcurrentModificationExample {
  static Map map = new HashMap();

  public static void main(String[] args) {
    map.put(1, false);
    map.put(2, false);
    map.put(3, false);
    map.put(4, false);

    Iterator iter = map.keySet().iterator();
    while(iter.hasNext()) {
      int value = iter.next();
      System.out.println("Value is: '" + value +"'");
      if (value == 3)
        map.put(5, false); // place where ConcurrentModificationException occurs
    }
  }
}
When I ran this code. I was able to reproduce the problem. Wala I knew exactly what was going on. After some googling I found couple of good resources and the way out. There are different solutions to this problem. They are:
  • Use the iterator to perform data structure manipulation
  • The Iterator provides manipulation by the remove() method. However in this case you would like to insert and not remove.
  • The other solution is to use ConcurrentHashMap .
  • blog
  • Use a synchronized block within your code.
However in my code I went ahead with ConcurrentHashMap and it worked fine. If time permits I would like to make this a multi threaded program. May become a topic for my later posts.