使用OSC时如何避免java.util.ConcurrentModificationException?

时间:2012-07-03 22:53:08

标签: java multithreading exception osc

我正在处理的代码是抛出上述异常。我对多线程编程不是很有经验,而且我没有很多运气对此进行故障排除。

该程序使用Processing和OSC以Java编写。主要的OSC事件处理程序是向Vector添加元素。它是在用户输入时触发的,因此非常难以预测。此Vector也在Processing的动画线程中进行迭代和更新,这种线程每秒约60次非常频繁地发生。

偶尔会调用OSC事件处理程序,因为Vector正在动画线程中进行迭代,并抛出异常。

我尝试将“synchronized”修饰符添加到OSC事件处理程序中。我还试图提示对Vector的更改,直到动画线程的下一帧(时间步),但我发现它最终会延迟抛出的异常。

我该怎么做才能防止这种行为?有没有办法只访问Vector,如果它还没有被使用?

更新 两个答案表明该列表在迭代时添加或删除了元素。这实际上是由于OSC正在从迭代列表的线程以外的线程触发处理程序的事实。我正在寻找一种方法来阻止这种情况。

这是一些伪代码:

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 个答案:

答案 0 :(得分:6)

关于您的代码

在你的代码中,你的for-each循环遍历列表,你的osEvent修改了列表。同时运行的两个线程可能会尝试:迭代列表,而其他线程则向其添加元素。你的for循环创建了一个迭代器。

您可以执行以下操作(前提是这些是发生这种情况的唯一两个地方):

//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 );
        }   
    }
}

或者,正如我在下面解释的那样,在迭代之前制作一个矢量副本,这可能会更好。

关于并发修改例外

此异常不一定在多线程代码中抛出。当您在迭代时修改集合时会发生这种情况。即使在单线程应用程序中,您也可以获得此异常。例如,在for-each循环中,如果您删除或添加元素到列表,最终会得到ConcurrentModificationException

因此,向代码添加同步不一定能解决问题。一些替代方法包括制作要迭代的数据的副本,或使用接受修改的迭代器(即ListIterator)或带有快照迭代器的集合。

显然,在多线程代码中,您仍然需要注意同步以避免进一步的问题。

让我举几个例子:

假设您希望在迭代时删除集合中的项目。避免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")));

在增强的for循环中收集要删除的所有记录,并在完成迭代后删除所有找到的记录。

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);

或者您可以在迭代过程中使用支持删除/添加方法的ListIterator

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

在多线程环境中,您可以考虑在迭代之前制作集合的副本,这样,允许其他人修改原始集合而不影响迭代:

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

或者,您可以考虑使用快照迭代器使用其他类型的集合,例如java.util.ConcurrentCopyOnWriteArrayList,它保证不抛出ConcurrentModificationException。但首先阅读文档,因为这种类型的集合并不适合所有场景。

答案 1 :(得分:2)

如果您想要独占访问权限,则需要锁定列表中的整个操作。 Vector在内部同步,但它仍会释放锁定,然后在迭代的每次传递中再次获取它。

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 :(得分:1)

如果在迭代期间从集合中添加/删除项目,则会发生这种情况。你可以研究一些事情:

  1. CopyOnWriteArrayList() - 相当昂贵,但可以帮助您避免并发修改异常,或者
  2. 使用concurrentHashMap
  3. 同步迭代本身

答案 3 :(得分:1)

正如已经提到的,当您迭代列表并在迭代期间更改内容时,会发生并发修改异常。对于使用集合的Iterator的某些类将解决此问题但不是所有集合实现Iterator。

尝试使用composition来包装迭代的集合,然后迭代获取锁,然后在迭代后释放锁。为了使任何添加,删除等工作,操作需要由同一个锁保护。

// 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();
  }
}

然后使用是做

之类的事情
 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();
     }
   }
 }