当一个线程调用一个共享变量的wait()方法时,调用该线程会被阻塞挂起,直到发生:
1. 其他线程调用了该共享对象的notify()或者notifyAll()方法。
2. 其他线程调用了该线程的interrupt()方法。
3. 该线程抛出了interruptedException异常返回。
需要额外注意的是,如果调用wait()方法的线程没有事先获取该对象的监视器锁,则调用wait()方法时调用线程回抛出IlleagalMonitorStateException异常。
一个线程如何获取一个共享变量的监视器锁呢?
1. 执行sync同步代码块时,使用该共享变量作为参数
synchronized(共享变量){
//doSome
}
2. 调用该共享变量的方法,并且该方法使用了sync修饰。
sync void add(int a,int b){
//doSome
}
虚假唤醒:
一个线程可以从挂起状态变为运行状态,在该线程没有被其他线程notify(),notifyAll()的方法通知,或者被中断,或者等待超时,这就是所谓的虚假唤醒。
不停去测试该线程被唤醒的条件是否满足,不满足则继续等待,在一个循环中调用wait()方法进行防范。退出循环时,就是满足了唤醒该线程的条件。
synchronized(obj){
while(条件不满足){
obj.wait();
}
}
以上方法是调用共享变量wait()方法的经典案例,首先通过同步块或者obj上面的监视器锁,然后在while循环内调用obj的wait()方法。
因为synchronized关键字拿到了共享变量的queue的监视器锁,所以调用wait()方法才不会抛出IllealgMonitorStateException的异常。如果当前队列没有空闲容量则调用queued的wait()方法挂起当前线程,这里使用循环就是避免虚假唤醒的问题。
假如当前线程被虚假唤醒了,但是队列还是没有空闲容量,那么当前线程还是会调用wait()方法把自己挂起。
public final static List<Integer> queue= new ArrayList<>(Arrays.asList(1,2,3,4,5));
public final static Integer MAX_SIZE=5;
//生产者线程
synchronized (queue){
while(queue.size()==MAX_SIZE){
try{
queue.wait();
}catch(Exception ex){
ex.printStackTrace();
}
}
queue.add(5);
queue.notifyAll();
}
//消费者线程
synchronized (queue){
while(queue.size()==0){
try {
queue.wait();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println( queue.get(1));
queue.notifyAll();
}
}