下面是一段简单的wait()方法调用的代码(并无实际意义):

1
2
3
synchronized(obj){
obj.wait();
}

​ 当一个线程调用了Object对象的wait()方法时,该线程会释放当前的锁对象,同时进入等待对象资源的状态,只有调用了notify()方法或者notifyAll()方法才能唤醒(随机唤醒,未必被唤醒)。

wait()方法应该只在获取了锁的对象之后才去调用,即wait()方法应该放在同步块中。如果不放在同步块中会怎样呢?

1
2
3
4
5
6
7
8
9
10
public class Test {
@org.junit.Test
public void test() {
try {
new Object().wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

​ 那么,为什么wait()notify()方法一定要在同步块中调用呢?

​ 经过一顿搜索之后,我找到了答案。

Lost Wake-Up Problem

​ 这是多线程编程里的一个经典的问题,并且这个问题并不只限于Java,而是所有的多线程环境。

​ 假设在生产者消费者问题中,有一个生产者线程,一个消费者线程。生产者生产资源,如果资源池满了则等待;消费者消费资源,如果资源池空了,则等待。如果资源池有资源,则可以唤醒消费者;如果资源池未满,则可以唤醒生产者。以下是简易版的生产者消费者问题代码:

生产者伪代码:

1
2
count+1; //资源池资源加一
notify();//资源池有资源,唤醒消费者

消费者伪代码:

1
2
3
while(count<=0)
wait();//资源池空了就等待
count--;

正常的情况应该是:

生产者:

1. 生产资源
2. 唤醒消费者

消费者:

1. 检查资源池
2. 继续等待或消费资源

但是在多线程环境下,如果不通过同步控制,则可能会出现步骤混乱的情况。比如说,初始的时候count等于0,这个时候消费者检查count的值,发现count小于等于0的条件成立;就在这个时候,发生了上下文切换,生产者进来了,噼噼啪啪一顿操作,把两个步骤都执行完了,也就是发出了通知,准备唤醒一个线程。这个时候消费者刚决定睡觉,还没睡呢,所以这个上面唤醒通知就会被丢掉。紧接着,消费者就睡过去了……

如果不加锁的话,那么可能调用wait()方法的时候已经不满足wait()的条件了。为了避免这样的问题,wait()notify()方法被强制只能在同步块中使用。