背景介绍
在Java中,我们使用wait()
和nofify()
或notifyAll()
来实现线程间通信。一个线程在测试条件不满足后进入等待状态。在经典的生产者-消费者问题中,生产者线程因缓存区满而等待,消费者线程在消费了缓存区的一个元素后通知生产者线程。
调用notify()
和notifyAll()
方法来通知一个或多个线程一个条件已经改变了。一旦通知线程退出同步方法或同步块,所有等待的线程会争抢它们等待对象上的对象锁。获取锁的线程会从等待状态返回并继续执行
代码示例
一般而言,使用Object的wait/notify进行现场通信代码如下图示,wait()
和notify()
都在同步块中执行.
若wait()
和notify()
不在同步块中执行,会出现什么情况呢?
/**
* @author pfjia
* @since 2018/3/6 15:40
*/
public class IllegalWaitDemo {
public static void main(String[] args) {
IllegalWaitDemo demo=new IllegalWaitDemo();
try {
demo.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
执行上述代码后,结果如下,抛出java.lang.IllegalMonitorStateException:
Exception in thread "main" java.lang.IllegalMonitorStateException
at java.base/java.lang.Object.wait(Native Method)
at java.base/java.lang.Object.wait(Object.java:516)
at pfjia.IllegalWaitDemo.main(IllegalWaitDemo.java:11)
但是为什么会抛出java.lang.IllegalMonitorStateException呢?
竞态条件思路
试想在不加锁时,一个如下的操作顺序
生产者
while (!someCondition){ (1)
lock.wait(); (4)
}
消费者
satisfyCondition(); (2)
lock.notify(); (3)
由于while(!someCondition){lock.wait()}
是一个先判断后执行的语句,因此一定存在竞态条件
,当上述代码以标识的顺序执行时,由于在!someCondition
为true后才执行satisfyCondition()
满足该condition,因此将导致即使condition被满足了,lock.wait()
还是会在lock.notify()
之后执行,就相当于丢失了一次唤醒.如果只有一个消费者线程,则生产者将永远得不到唤醒.
PV操作思路
我是将
synchronized (lock){
while (!someCondition){ (1)
lock.wait(); (4)
}
}
看做P操作.
将
synchronized (lock){
satisfyCondition(); (2)
lock.notify(); (3)
}
看做V操作,而PV的资源含义则由someCondition
和satisfyCondition()
确定.
由于PV操作必须是原子操作,而若wait()
和notify()
不在同步块中,就无法保证原子性
,所以wait()
和notify()
必须在同步块中执行.