Saturday, May 01, 2010

ConcurrentModificationException - How to avoid / remove it?

As per the Java Doc:

This exception may be thrown by methods that have detected concurrent modification of an object when such modification is not permissible. For example, it is not generally permissible for one thread to modify a Collection while another thread is iterating over it.

Caused by Fail Fast Iterators, any collection using these iterators can throw the CME. They happen because of mainly 2 reasons -

  • The state of the iterator gets changed by the same/other thread in such a fashion that all other references feel that their understanding of the iterator's state is dirty. For instance, threadA is iterating using iterator.next() and some other threadB takes the reference of the iterator and removes some entry from the collection, hence threadA will get hinted that it's knowledge about iterator state is not right, in other words it has become dirty now. Even, one important point is that there is no requirement to have another thread to cause CME, it can happen because of the same thread itself. In single thread, it can happen when you are iterating over the collection and calling collectionImpl.remove or collectionImpl.add, which causes the iterator reference to feel like it has been fooled, as it's state is going to be changed.
  • Another reason could be that the underlying collection is not initialized and some other thread has started iterating over it.

A code like below will throw concurrent modification exception,

  1: import java.util.List;
  2: import java.util.ArrayList;
  3: import java.util.Iterator;
  4:
  5: public class Sample{
  6:
  7: List<String> strings = new ArrayList<String>();
  8:
  9: public void fillList(){
 10:  for(int i=0;i<10;i++){
 11:   strings.add(""+i);
 12:  }
 13: }
 14:
 15: public void iterateList(){
 16: /*WRONG WAY, will produce CME
 17: *Iterator reference is old,
 18: *iterator's idea of collections' state becomes dirty
 19: * after collectionImpl.remove/add/etc
 20: */
 21:  Iterator<String> itr = strings.iterator();
 22:  strings.remove("7");
 23:  while(itr.hasNext()){
 24:   System.out.println(itr.next());
 25:  }
 26: }
 27:
 28: public static void main(String args[]){
 29: Sample s = new Sample();
 30: s.fillList();
 31: s.iterateList();
 32: }
 33: }
 34: 


The solutions to avoid ConcurrentModificationException are as follows:



1. Use Weakly Consistent Iterators - JavaSE 1.5 and JavaSE 1.6 has many collection implementation which uses Weakly Consistent Iterators, which don't throw CME.




  • CopyOnWriteArraySet, CopyOnWriteArrayList (Java SE 5): They copy an internal array on each modification. Hence, make sure that when you are using these implementation your usage is more of iteration than modification to them.


  • ConcurrentSkipListSet, ConcurrentSkipListMap (Java SE 6): They are skip list based implementation, provides concurrency with sorting.


  • ConcurrentHashMap (Java SE 6): provides extra atomic methods.



2. Make sure you are using iterator in right way,



  1: import java.util.List;
  2: import java.util.ArrayList;
  3: import java.util.Iterator;
  4:
  5: public class Sample{
  6:
  7: List<String> strings = new ArrayList<String>();
  8:
  9: public void fillList(){
 10:  for(int i=0;i<10;i++){
 11:   strings.add(""+i);
 12:  }
 13: }
 14:
 15: public void iterateList(){
 16: // RIGHT WAY 1
 17: //Get a new reference to the iterator
 18:
 19: /*
 20:  strings.remove("7");
 21:  for(String str:strings){
 22:   System.out.println(str);
 23:  }
 24: */
 25:
 26:
 27: //OR RIGHT WAY 2
 28: // Try to remove the element from Iterator reference,
 29: // so that iterator doesn't become dirty
 30:
 31: /*
 32:  Iterator<String> itrNew = strings.iterator();
 33:  while(itrNew.hasNext()){
 34:  String str = itrNew.next();
 35:  if("7".equals(str)){
 36:    itrNew.remove();
 37:   }else{
 38:    System.out.println(str);
 39:   }
 40:  }
 41: */
 42:
 43:
 44: //OR RIGHT WAY 3, quite similar to RIGHT WAY 1
 45: //Get the itr reference after your collection modification operation
 46: /*
 47:  strings.remove("7");
 48:  Iterator<String> itrOther = strings.iterator();
 49:  while(itrOther.hasNext()){
 50:   System.out.println(itrOther.next());
 51:  }
 52: */
 53: }
 54:
 55: public static void main(String args[]){
 56:  Sample s = new Sample();
 57:  s.fillList();
 58:  s.iterateList();
 59: }
 60:
 61: }


3. Make sure that your collection instance are populated before your thread start iterating over them.



  1: class MyClass {
  2:
  3: private final List myList = makeList();
  4:
  5: private static list makeList() {
  6:  List list = new ArrayList();
  7:  // do what you need to initialize this 
  8:  return list;
  9:  }
 10: }
 11: 


ConcurrentSet are not included in Java 1.6, as Collections class provides a convenient method newSetFromMap which returns a set backed by map.



If you want you can use Decorator pattern to write a ConcurrentSet which takes a regular Set are constructor arguments and inside the class uses a ConcurrentHashMap<E, BOOLEAN> to give concurrent set.

No comments: