下面是一段简单的wait()
方法调用的代码(并无实际意义):
1 | synchronized(obj){ |
当一个线程调用了Object对象的wait()
方法时,该线程会释放当前的锁对象,同时进入等待对象资源的状态,只有调用了notify()
方法或者notifyAll()
方法才能唤醒(随机唤醒,未必被唤醒)。
wait()
方法应该只在获取了锁的对象之后才去调用,即wait()
方法应该放在同步块中。如果不放在同步块中会怎样呢?
1 | public class Test { |
那么,为什么wait()
和notify()
方法一定要在同步块中调用呢?
经过一顿搜索之后,我找到了答案。
Lost Wake-Up Problem
这是多线程编程里的一个经典的问题,并且这个问题并不只限于Java,而是所有的多线程环境。 假设在生产者消费者问题中,有一个生产者线程,一个消费者线程。生产者生产资源,如果资源池满了则等待;消费者消费资源,如果资源池空了,则等待。如果资源池有资源,则可以唤醒消费者;如果资源池未满,则可以唤醒生产者。以下是简易版的生产者消费者问题代码:
生产者伪代码:
1 | count+1; //资源池资源加一 |
消费者伪代码:
1 | while(count<=0) |
正常的情况应该是:
生产者:
1. 生产资源
2. 唤醒消费者
消费者:
1. 检查资源池
2. 继续等待或消费资源
但是在多线程环境下,如果不通过同步控制,则可能会出现步骤混乱的情况。比如说,初始的时候count等于0,这个时候消费者检查count的值,发现count小于等于0的条件成立;就在这个时候,发生了上下文切换,生产者进来了,噼噼啪啪一顿操作,把两个步骤都执行完了,也就是发出了通知,准备唤醒一个线程。这个时候消费者刚决定睡觉,还没睡呢,所以这个上面唤醒通知就会被丢掉。紧接着,消费者就睡过去了……
如果不加锁的话,那么可能调用wait()
方法的时候已经不满足wait()
的条件了。为了避免这样的问题,wait()
和notify()
方法被强制只能在同步块中使用。