4

我正在处理的代码正在引发上述异常。我对多线程编程不是很有经验,而且我没有很多运气来解决这个问题。

该程序是使用处理和 OSC 用 Ja​​va 编写的。主要的 OSC 事件处理程序正在向 Vector 添加元素。它是在用户输入时触发的,因此高度不可预测。这个向量也在 Processing 的动画线程中被迭代和更新,它以每秒大约 60 次的速度非常有规律地发生。

有时,当 Vector 在动画线程中被迭代并抛出异常时,会调用 OSC 事件处理程序。

我尝试将“ 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

4 回答 4

8

关于您的代码

在您的代码中,您的 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),或具有快照迭代器的集合。

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

让我举几个例子:

假设您想在迭代集合时从集合中删除项目。您避免 a 的替代方法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在迭代过程中使用支持删除/添加方法的 a。

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。但是先看文档,因为这种类型的集合并不适合所有的场景。

于 2012-07-03T22:56:56.747 回答
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();
    }
}
于 2012-07-03T23:22:10.680 回答
1

如果在迭代期间从集合中添加/删除项目,则会发生这种情况。您可以查看的一些内容:

  1. CopyOnWriteArrayList() - 相当昂贵但可以帮助您避免并发修改异常,或者
  2. 使用并发HashMap
  3. 在迭代本身上同步
于 2012-07-03T23:06:08.457 回答
1

正如已经提到的,并发修改异常发生在您迭代列表并且迭代期间内容发生变化时。对于某些使用集合的迭代器的类将解决这个问题,但并非所有集合都实现迭代器。

尝试使用组合来包装正在迭代的集合,并在迭代之前获得锁,然后在迭代之后释放锁。为此,任何添加、删除等操作都需要由同一个锁保护。

// 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();
     }
   }
 }
于 2012-07-03T23:26:07.787 回答