2017-02-09 1 views
1

wait()notify()のサンプルプログラムを実行していますが、notify()が呼び出されたときに複数のスレッドが1つではなく起動します。通知が複数のスレッドを呼び起こしているようです

コードは次のとおりです。

public class MyQueue<T> { 

    Object[] entryArr; 
    private volatile int addIndex; 

    private volatile int pending = -1; 
    private final Object lock = new Object(); 

    private volatile long notifiedThreadId; 
    private int capacity; 

    public MyQueue(int capacity) { 
     entryArr = new Object[capacity]; 
     this.capacity = capacity; 
    } 

    public void add(T t) { 
     synchronized (lock) { 
      if (pending >= 0) { 
       try { 
        pending++; 
        lock.wait(); 
        System.out.println(notifiedThreadId + ":" + Thread.currentThread().getId()); 
       } catch (InterruptedException e) { 
        e.printStackTrace(); 
       } 
      } else if (pending == -1) { 
       pending++; 
      } 
     } 

     if (addIndex == capacity) { // its ok to replace existing value 
      addIndex = 0; 
     } 

     try { 
      entryArr[addIndex] = t; 
     } catch (ArrayIndexOutOfBoundsException e) { 
      System.out.println("ARRAYException:" + Thread.currentThread().getId() + ":" + pending + ":" + addIndex); 
      e.printStackTrace(); 
     } 

     addIndex++; 

     synchronized (lock) { 
      if (pending > 0) { 
       pending--; 
       notifiedThreadId = Thread.currentThread().getId(); 
       lock.notify(); 
      } else if (pending == 0) { 
       pending--; 
      } 
     } 
    } 

} 

public class TestMyQueue { 

    public static void main(String args[]) { 
     final MyQueue<String> queue = new MyQueue<>(2); 

     for (int i = 0; i < 200; i++) { 
      Runnable r = new Runnable() { 
       @Override 
       public void run() { 
        for (int i = 0; i < Integer.MAX_VALUE; i++) { 
         queue.add(Thread.currentThread().getName() + ":" + i); 
        } 
       } 
      }; 
      Thread t = new Thread(r); 
      t.start(); 
     } 
    } 

} 

いくつかの時間後、私は2つのスレッドがシングルスレッドでウェイクアップされて参照してください。出力は次のようになります。

91:114 
114:124 
124:198 
198:106 
106:202 
202:121 
121:40 
40:42 
42:83 
83:81 
81:17 
17:189 
189:73 
73:66 
66:95 
95:199 
199:68 
68:201 
201:70 
70:110 
110:204 
204:171 
171:87 
87:64 
64:205 
205:115 

115スレッドは2つのスレッドに通知され、84スレッドは2つのスレッドに通知されます。このため、ArrayIndexOutOfBoundsExceptionが表示されています。

115:84 

115:111 

84:203 

84:200 

ARRAYException:200:199:3 

ARRAYException:203:199:3 

プログラムの問題点は何ですか?

+2

実際には 'synchronized'の目的が間違っているようです。これは、共有リソースへのアクセスを保護するためであり、共有リソースに完全に保護されていない状態でアクセスしている間は、待機して通知するのではありません。さらに、['Object.wait()'](https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#wait--)のドキュメントをよく読んでください。特に "* ...偽のウェークアップが可能で、このメソッドは常にループ内で使用する必要があります*"の部分です。 – Holger

+0

ご返信ありがとうございます。私は同時にロックを使用できることを知っています。しかし、私の仕事はwait()とnotify()を使ってロックを作ることです。したがって、いつでも、同期されたブロック間でコードを実行するのは1つのスレッドだけです。 – CodingJDev

+0

私のコメントで「同時ロック」について何か言いましたか?'synchronized'ブロック*は、' wait'または 'notify'を実行する部分だけでなく、共有データ構造へのアクセスを含むすべての操作*にまたがっていなければなりません。 'synchronized'ブロックの外側にある' entryArr'と 'addIndex'にアクセスしていて、' volatile'として 'addIndex'を宣言すると、更新をアトミックにしないので助けになりません。 – Holger

答えて

1

プログラムの問題点は何ですか?

この現象を引き起こしているコードには、いくつか問題があります。まず、@Holderがコメントしたように、​​ブロックを使用して保護する必要がある複数のスレッドによって同時に実行できるコードセグメントがたくさんあります。例えば

if (addIndex == capacity) { 
    addIndex = 0; 
} 

複数のスレッドがこれを実行する場合は、複数のスレッドがaddIndex == capacityと複数の0番目のインデックスを上書きされます表示される場合があります。もう1つの例は次のとおりです。

addIndex++; 

これは、2つのスレッドが同時にこのステートメントを実行しようとすると競合が発生します。 addIndexがあらかじめ0だった場合、2つのスレッドがこのステートメントを実行した後、競合条件によってはaddIndexの値が1または2になる可能性があります。

複数のスレッドによって同時に実行されるステートメントは、​​ブロック内に正しくロックされている必要があります。 volatile個のフィールドがあっても、複数の操作が実行されているため競合状態が続くことがあります。

また、古典的な間違いは、あなたの配列上のオーバーフローまたはアンダーフローをチェックするときにifステートメントを使用することです。クラスコンシューマのプロデューサ競合条件がないことを確認するには、whileステートメントにする必要があります。 my docs hereを参照するか、関連する質問をご覧ください。Why does java.util.concurrent.ArrayBlockingQueue use 'while' loops instead of 'if' around calls to await()?

関連する問題