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
プログラムの問題点は何ですか?
実際には 'synchronized'の目的が間違っているようです。これは、共有リソースへのアクセスを保護するためであり、共有リソースに完全に保護されていない状態でアクセスしている間は、待機して通知するのではありません。さらに、['Object.wait()'](https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#wait--)のドキュメントをよく読んでください。特に "* ...偽のウェークアップが可能で、このメソッドは常にループ内で使用する必要があります*"の部分です。 – Holger
ご返信ありがとうございます。私は同時にロックを使用できることを知っています。しかし、私の仕事はwait()とnotify()を使ってロックを作ることです。したがって、いつでも、同期されたブロック間でコードを実行するのは1つのスレッドだけです。 – CodingJDev
私のコメントで「同時ロック」について何か言いましたか?'synchronized'ブロック*は、' wait'または 'notify'を実行する部分だけでなく、共有データ構造へのアクセスを含むすべての操作*にまたがっていなければなりません。 'synchronized'ブロックの外側にある' entryArr'と 'addIndex'にアクセスしていて、' volatile'として 'addIndex'を宣言すると、更新をアトミックにしないので助けになりません。 – Holger