4

私が取り組んでいるコードは、前述の例外をスローしています。私はマルチスレッド プログラミングの経験があまりなく、これをトラブルシューティングする運があまりありません。

このプログラムは、Processing と OSC を使用して Java で記述されています。メインの OSC イベント ハンドラーは、ベクターに要素を追加しています。これはユーザー入力でトリガーされるため、非常に予測不可能です。このベクターは、Processing のアニメーション スレッドでも繰り返し処理され、更新されます。これは、1 秒あたり約 60 回の頻度で非常に定期的に発生します。

ベクターがアニメーション スレッドで反復処理されているときに OSC イベント ハンドラーが呼び出され、例外がスローされることがあります。

synchronizedOSC イベント ハンドラに" " 修飾子を追加しようとしました。また、アニメーション スレッドの次のフレーム (時間ステップ) まで Vector への変更をキューに入れようとしましたが、例外がスローされるのを遅らせるだけであることがわかりました。

この動作を防ぐにはどうすればよいですか? ベクターがまだ使用されていない場合にのみアクセスする方法はありますか?

更新: 2 つの回答は、反復処理中にリストに要素が追加または削除されていることを示唆しています。これは、リストを繰り返し処理しているスレッド以外のスレッドから 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変更しています。同時に実行されている 2 つのスレッドが次のことを試みている可能性があります。for ループはイテレータを作成します。

次のことができます (これが発生する場所がこれらの 2 つだけである場合)。

//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、反復自体中に remove/add メソッドをサポートする を使用することもできます。

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

排他的なアクセスが必要な場合は、リストの操作全体をロックする必要があります。ベクトルは内部的に同期されますが、ロックを解放し、反復の各パスで再度取得します。

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

既に述べたように、同時変更の例外は、リストを繰り返し処理しているときに内容が変更されたときに発生します。コレクションの Iterator を使用する特定のクラスではこれが解決されますが、すべてのコレクションが Iterator を実装しているわけではありません。

コンポジションを使用して、反復されるコレクションをラップし、反復する前にロックを取得してから、反復後にロックを解放してみてください。これが追加、削除などを機能させるには、操作を同じロックで保護する必要があります。

// 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 に答える
1

これは、反復中にアイテムがコレクションに追加/コレクションから削除された場合に発生します。あなたが調べることができるいくつかのこと:

  1. CopyOnWriteArrayList() - 非常にコストがかかりますが、同時変更の例外を回避するのに役立つ場合があります。または
  2. 並行ハッシュマップを使用する
  3. 反復自体で同期する
于 2012-07-03T23:06:08.457 に答える