How to avoid java.util.ConcurrentModificationException when using OSC?

The code I'm working on throws the above exception. I am not very experienced with multi-threaded programming, and I do not have much luck in fixing this problem.

The program is written in Java using Processing and OSC. The main OSC event handler adds elements to the vector. It starts when a user enters and is therefore extremely unpredictable. This vector is also repeated and updated in the processing of the animation thread, which occurs very often about 60 times per second.

Sometimes an OSC event handler is called when a vector is looped through the animation stream and an exception is thrown.

I tried to add the " synchronized " modifier to the OSC event handler. I also tried to make changes to the vector until the next frame (time step) of the animation stream, but I found that it just ends up throwing an exception.

What can I do to prevent this behavior? Is there a way to access Vector if it is not already in use?

Update: Two answers suggested that the list has items added or removed when they are repeated. This is actually due to the fact that OSC starts the handler from a thread other than a thread that iterates through the list. I am looking for a way to prevent this.

Here are a few pseudo codes:

 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 ); } 
+4
source share
4 answers

About your code

In your code, the for-each loop repeats through the list, and your osEvent modifies the list. Two threads working simultaneously can try: iterate over the list, and the other - add elements to it. Your for loop creates an iterator.

You can do the following (assuming these are only two places where this happens):

 //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 repeating it, which is likely to be better.

About the exception of parallel modification

This exception is not necessarily thrown in multi-threaded code. This happens when you modify a collection while it is repeating. You can get this exception even in single-threaded applications. For example, in a for-each loop, if you delete or add items to the list, you get a ConcurrentModificationException .

Thus, adding synchronization to the code will not necessarily solve the problem. Some alternatives are to copy duplicate data or use iterators that accept the changes (like ListIterator) or a collection of snapshot iterators.

Obviously, in the multi-threaded part of the code, you still have to take care of synchronization to avoid further problems.

Let me give you a few examples:

Suppose you want to remove items from a collection, iterate over it. Your alternatives to avoid ConcurrentModificationException :

 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 in the extended loop, and after the iteration is completed, you will delete all the records found.

 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 can use ListIterator , which supports the 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 multi-threaded environment, you might consider creating a copy of the collection before iterating, allowing others to modify the original collection without affecting the iteration:

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

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

+6
source

If you need exclusive access, you need to block the entire operation in the list. The vector is internally synchronized, but it still releases the lock, and then gets it again on each iteration pass.

 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(); } } 
+2
source

This will happen if items are added / removed from the collection during iteration. Some things that you can pay attention to:

  • CopyOnWriteArrayList () is quite expensive, but can help avoid the exception of parallel modifications, or
  • Use concurrentHashMap
  • Synchronize the iteration itself
+1
source

As already mentioned, concurrency modification exception occurs when you repeat the list and contents change during iteration. For some classes using the Collection Iterator, this will allow, but not the entire Iterator set to implement the collection.

Try using the composition to wrap the recycled collection and obtain a lock before reusing it, and then release the lock after iteration. In order for this to work with any addition, deletion, etc., Operations had to be protected with the same lock.

 // example only public class LockingVector { private final Vector v; private final ReentrantLock lock = new ReentrantLock(); public void lock(){ lock.lock(); } public void unlock(){ lock.unlock(); } // other 'vector' method delegated to v public Object get() { return v.get(); } } 

Then you need to use something like

  public class Main { public static void main(String[] args){ Vector v = ... LockingVector lv = new LockingVector(v); try { lv.lock(); // do stuff here (add, delete, iterate, etc.) } finally { lv.unlock(); } } } 
+1
source

All Articles