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.