Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can java.util.ConcurrentModificationException be avoided when using OSC?

The code I am working on is throwing the aforementioned exception. I am not very experienced with multi-threaded programming and I'm not having a lot of luck troubleshooting this.

The program is written in Java using Processing and OSC. The main OSC event handler is adding elements to a Vector. It is triggered on user input and therefore highly unpredictable. This Vector is also being iterated over and updated in Processing's animation thread which happens very regularly at about 60 times per second.

Occasionally, the OSC events handler is called as the Vector is being iterated over in the animation thread and the exception is thrown.

I have tried adding the "synchronized" modifier to the OSC event handler. I have also attempted to cue changes to the Vector until the next frame ( time step ) of the animation thread, but I'm finding that it just ends up delaying the exception being thrown.

What can I do to prevent this behavior? Is there a way to only access to the Vector if it isn't already in use?

Update: Two answers have suggested that the list is having elements added or removed as it is being iterated over. This is in fact what is happening due to the fact that OSC is triggering the handler from a thread other than the thread that is iterating over the list. I am looking for a way to prevent this.

Here is some pseudo-code:

Vector<String> list = new Vector<String>();
Vector<Particle> completedParticles = new Vector<Particle>();

public void oscEvent( OSCMessage message )
{
    list.add( new Particle( message.x, message.y ) );
}

public void draw()
{
    completedParticles.clear();
    for( Particle p : list )
    {
        p.draw();
        if( p.isComplete ) {
            completedParticles.add( p );
        }   
    }
    list.removeAll( completedParticles );
}
like image 317
JeremyFromEarth Avatar asked Dec 15 '22 22:12

JeremyFromEarth


2 Answers

About your Code

In your code, your for-each loop is iterating over the list, and your osEvent modifies the list. Two threads, running simultaneously could be trying to: iterate over the list while other is adding elements to it. Your for loop creates an iterator.

You can do the following (provided that these are the only two places where this occurs):

//osEvent
synchronized(this.list) {
   list.add( new Particle( message.x, message.y ) );
}

//draw
synchronized(this.list) {
  for( Particle p : list )
    {
        p.draw();
        if( p.isComplete ) {
            completedParticles.add( p );
        }   
    }
}

Or, as I explain below, make a copy of the vector before iterating over it, that will probably be better.

About Concurrent Modification Exception

This exception is not necessarily thrown in a multithreaded code. It happens when you modify a collection while it is being iterated. You can get this exception even in single-threaded applications. For instance, in a for-each loop, if you remove or add elements to a list, you end up getting a ConcurrentModificationException.

As such, adding synchronization to the code will not necessarily solve the problem. Some alternatives consist in making a copy of the data to be iterated, or using iterators that accept modifications (i.e. ListIterator), or a collection with snapshot iterators.

Evidently, in a multithreaded piece of code, you would still have to take care of synchronization to avoid further problems.

Let me give some examples:

Let's say you want to delete items from a collection while iterating over it. Your alternatives to avoid a ConcurrentModificationException are:

List<Book> books = new ArrayList<Book>();
books.add(new Book(new ISBN("0-201-63361-2")));
books.add(new Book(new ISBN("0-201-63361-3")));
books.add(new Book(new ISBN("0-201-63361-4")));

Collect all the records that you want to delete within an enhanced for loop, and after you finish iterating, you remove all found records.

ISBN isbn = new ISBN("0-201-63361-2");
List<Book> found = new ArrayList<Book>();
for(Book book : books){
    if(book.getIsbn().equals(isbn)){
        found.add(book);
    }
}
books.removeAll(found);

Or you may use a ListIterator which has support for a remove/add method during the iteration itself.

ListIterator<Book> iter = books.listIterator();
while(iter.hasNext()){
    if(iter.next().getIsbn().equals(isbn)){
        iter.remove();
    }
}

In a multithreaded environment, you might consider making a copy of the collection before iterating, as such, allowing others to modify the original collection without affecting iteration:

synchronized(this.books) {
   List<Book> copyOfBooks = new ArrayList<Book>(this.books)
}
for(Book book : copyOfBooks) {
   System.out.println(book);
}

Alternatively, you may consider using other types of collections using snapshot iterators, like java.util.ConcurrentCopyOnWriteArrayList which guarantees not to throw ConcurrentModificationException. But read the documentation first, because this type of collection is not suitable for all scenarios.

like image 86
Edwin Dalorzo Avatar answered Mar 02 '23 00:03

Edwin Dalorzo


If you want exclusive access, you need to lock around the entire operation on the list. Vector is internally synchronized, but it still releases the lock and then gets it again on each pass of the iteration.

java.util.concurrent.locks.Lock lock = new java.util.concurrent.locks.ReentrantLock();
Vector<String> list = new Vector<String>();
Vector<Particle> completedParticles = new Vector<Particle>();

public void oscEvent( OSCMessage message )
{
    lock.lock();
    try {
      list.add( new Particle( message.x, message.y ) );
    } finally {
      lock.unlock();
    }
}

public void draw()
{
    completedParticles.clear();
    lock.lock();
    try {
      for( Particle p : list )
      {
          p.draw();
          if( p.isComplete ) {
              completedParticles.add( p );
          }   
      }
      list.removeAll( completedParticles );
    } finally {
      lock.unlock();
    }
}
like image 32
Affe Avatar answered Mar 02 '23 01:03

Affe