2016-04-07 22 views
2

メインメソッドに1つのリストがあり、このリストを使用するために2つのスレッドを書きたいと思います。ときどき私はIndexOutOfBoundsExceptionを(スレッドがremoveメソッドを呼び出すと)synchronizedブロックで捕捉します。IndexOutOfBoundsException 2つのスレッドで同時に実行されるlist.remove

主な方法:

public class PC { 
    public static void main(String[] args) { 
     List<String> strings = new ArrayList<>(); 
     new Costumer("c1", strings).start(); 
     new Costumer("c2", strings).start(); 
     new Producer("p1", strings).start(); 
     new Producer("p2", strings).start(); 
     new Producer("p3", strings).start(); 
     new Producer("p4", strings).start(); 
    } 
} 

貸衣装クラス:

class Costumer extends Thread { 

    List<String> strings; 
    public Costumer(String n, List<String> strings) { 
     super(n); 
     this.strings = strings; 
    } 
    @Override 
    public void run() { 
     while (true) { 
      synchronized (strings) { 
       try { 
        if (strings.isEmpty()) { 
         strings.wait(); 
        } 
        strings.remove(0); // <- where exception is thrown 
       } catch (InterruptedException ex) { 
       } 
      } 
     } 
    } 
} 

プロデューサークラス:

class Producer extends Thread { 

    List<String> strings; 

    public Producer(String n, List<String> strings) { 
     super(n); 
     this.strings = strings; 
    } 

    @Override 
    public void run() { 
     while (true) { 
      synchronized (strings) { 
       strings.add(String.valueOf(Math.random() * 1000)); 
       if (strings.size() == 1) { 
        strings.notify(); 
       } 
      } 
      try { 
       Thread.sleep(1000); 
      } catch (InterruptedException ex) { 
      } 
     } 
    } 
} 

スタックトレース:

Exception in thread "c2" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0 
     at java.util.ArrayList.rangeCheck(Unknown Source) 
     at java.util.ArrayList.remove(Unknown Source) 
     at Costumer.run(PC.java:40) 
+1

これは間違いなくこのコードで得られるエラーではありません。 'Arrays.asList'は何を返すと思いますか?あなたはjavadocを読んだことがありますか? – Savior

+0

また、文字列を生成してリストに入れる別のスレッドがあります。私はそれを要約した。メインコードには新しいArraylistがあります。 –

+0

そのため、実際に問題が再現された場合は、独自のコードをテストすることになっています。 Btw:リストの初期化を修正した後でも、このコードが問題を再現できるとは思いません。したがって、あなたのスレッドに通知するコードをポストすることもできます(これにより、そのリストが補充される可能性があります)。 – Tom

答えて

3

あなたのコードの問題は、Costumerクラスのifテストですが、whileループで置き換える必要があります。そうでなければ、競合状態の問題に直面する可能性があります。実際に、通知を受けるのを待っているコンシューマが1つあり、文字列のロックを待っているコンシューマが1つあり、ストリングをロックしているプロデューサがあり、新しい文字列が追加され、文字列がなくなったので通知します。したがって、ロックを解除すると、ロックを待っているコンシューマが最初にそれを取得するとします(通知されたコンシューマがまだロックを取得する必要があり、最初にロックを取得する必要がないことを忘れないでください)。 Stringを削除すると、コンシューマによって通知された2番目のコンシューマはstrings.wait()から開始され、strings.remove(0)が空であるかどうかをチェックせずにIndexOutOfBoundsExceptionを呼び出します。

つまりコードは次のようになります。

@Override 
public void run() { 
    while (true) { 
     synchronized (strings) { 
      try { 
       while (strings.isEmpty()) { 
        strings.wait(); 
       } 
       strings.remove(0); 
      } catch (InterruptedException ex) { 
      } 
     } 
    } 
} 

はとにかくこのような奇妙なバグを回避するために、whileループの中にあなたの状態をラップすることをお勧めします。たとえば、ArrayBlockingQueueのようなクラスでどのように行われているかを確認することができます。すべての条件はwhileループでチェックされます。

+0

* "彼らは両方とも最初の文字列を削除します" *そしてなぜそれを考えるのですか? OPは複数のプロデューサーを使用していると思いますから? – Tom

+0

なぜですか?わかりません。ただ1つのスレッドが通知で目を覚ます。 Notify docs: "このオブジェクトのモニタで待機している1つのスレッドを起動します。" –

+1

@ mohammad_1m2これは競合状態の問題で、いくつかのケースがあります。 1つのコンシューマが通知を待っています。文字列のロックを待っているコンシューマが1つあり、ストリングをロックしているプロデューサがあり、新しい文字列が追加され、文字列がなくなったので通知します。したがって、ロックを解除すると、ロックを待機しているコンシューマが最初に取得し、次にStringを削除した場合、通知された2番目のコンシューマはstrings.wait()から開始します。 strings.remove(0)を呼び出します。それが空であるかどうかチェックすることなく=> .IOOBE –

関連する問題