我正在做一個示例程序wait()
和notify()
,但是當調用notify()
時,多個線程被喚醒而不是一個。通知似乎在喚醒多個線程
的代碼是:
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();
}
}
}
一段時間後,我看到兩個螺紋爲喚醒由單個線程。輸出如下:
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個線程通知了兩個線程,而84個線程通知了兩個線程;因爲這個,我們看到了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
感謝您的快速回復。我知道我們可以使用併發Lock。但我的任務是使用wait()和notify()進行鎖定。所以在任何時候,只有一個線程應該執行同步塊之間的代碼。 – CodingJDev
在我的評論中,我說過什麼關於「併發鎖定」?'synchronized'塊*必須跨越整個操作*,包括對共享數據結構的每次訪問,而不僅僅是執行「等待」或「通知」的部分。你正在''synchronized''塊之外訪問'entryArr'和'addIndex',並且聲明'addIndex'爲'volatile'並不會幫助,因爲它不會使更新成爲原子。 – Holger