wait()放while里面算是一个常识性的准则。为什么要这样呢,如果放到if里面会有什么后果?今天水木有人贴出了一段出错的代码,对这个问题现身说法:
public class A {
private Object[] queue = new Object[1024];
private int cMsg;
public synchronized boolean accept(Object msg, Object token) {
if (cMsg >= queue.length) {
try {
wait();
}
catch (InterruptedException e) {
return false;
}
}
queue[cMsg++] = token;
queue[cMsg++] = msg;
return true;
}
public synchronized Object[] getMessages() {
if (cMsg == 0) {
return null;
}
Object[] tmp = (Object[]) Arrays.copyOf(queue, cMsg);
Arrays.fill(queue, 0, cMsg, null);
cMsg = 0;
notify();
return tmp;
}
}
这个代码在大并发下测试,抛出了java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 1025异常。
要分析原因的话,就是wait()被唤醒后,队列已经满了,cMsg >= queue.length这个条件已经不满足了,再往后移下标的话就数组越界了。
问题是为什么wait()唤醒后队列会满。在代码里,将队列清空后,才执行notify(),这个时候它应该只唤醒了一个线程,那么谁把队列填满的呢
答案是一个阻塞在accept上面的线程。首先要知道一点:其他线程收到信号并不是在notify调用的那一刻!notify的信号是在退出同步函数后才发出的,从退出同步函数,到信号发出,这中间有个时间差,因而就有可能出现以下执行序列:
1. getMessages方法中的 notify()调用
2. getMessages退出,此线程A释放类实例上的monitor
3. 一个阻塞在accept上的线程B,得以进入accept方法,因为此时数组被清空,线程B填入数据,下表+2;accept不断的调用,直到数组被填满,而阻塞在wait()调用上
4. notify信号发出,一个线程C被唤醒。这时没有再判断数组下标位置,直接想数组中塞数据,数组越界。
解决这个问题的方法当然就是把if改为while:
while (cMsg >= queue.length) {
try {
wait();
}
catch (InterruptedException e) {
return false;
}
}
在被唤醒后,重新判断条件是否满足。