1 为什么wait/notify必须要强制要求放在synchronized中
在日常开发,我们都知道wait/notify有着非常固定的一套模板,就是下面的样子,synchronized同步块包裹着Object.wait()
方法,如果不通过同步块包住的话JVM会抛出IllegalMonitorStateException
异常。
synchronized(lock) {
while(!condition){
lock.wait();
}
}
那么为什么要限制这么写呢?
2 如果Object.wait()/notify不需要同步
假设我们自己实现了一个BlockingQueue的代码。 如果Object.wait()/notify不需要同步,那么我们的代码会形如下面这样。
class BlockingQueue {
Queue<String> buffer = new LinkedList<String>();
public void give(String data) {
buffer.add(data);
notify(); // 往队列里添加的时候notify,因为可能有人在等着take
}
public String take() throws InterruptedException {
while (buffer.isEmpty()) // 用while,防止spurious wakeups(虚假唤醒)
wait(); // 当buffer是空的时候就等着别人give
return buffer.remove();
}
}
如果上面的代码可以执行的话,多线程情况下会出现一种情况:
1、 当前buffer是空的,这时来了一个take的请求,尚未执行到wait语句
2、 同时又来了一个give请求,完整执行完了整个give方法并且发送了notify
3、 此时take方法才走到wait,因为它错过了上一个notify,所以会在明明buffer不空的情况下挂起线程,take方法挂起。假如再没有人调用过give方法了,在业务上的表现就会是这个take线程永远也取不到buffer中的内容
3 为什么要在JVM层面抛异常
因为你只要用notify,那就是为了在多线程环境下同步,notify/wait机制本身就是为了多线程的同步而存在的,那就只能配套synchronized,所以为了防止上面情况的发生,就直接强制抛异常来限制开发的代码模式了。
4 如果没有wait/notify
试想一种场景,如果没有wait/notify的挂起唤醒机制,该如何实现BlockingQueue
class BlockingQueue {
Queue<String> buffer = new LinkedList<String>();
public void give(String data) {
buffer.add(data);
}
public String take() throws InterruptedException {
while(buffer.isEmpty) {
sleep(10);
}
return buffer.remove();
}
}
如果没有wait/notify,那么在take的时候只能通过while循环不停轮询判断buffer是否为空来实时获取buffer的最新状态,那么势必会造成两种情况:
1、 sleep时间过短,那么线程将一直不行循环抢占CPU,造成CPU打满
2、 sleep时间过长,那么将会影响take的时效性
综上,针对于BlockingQueue这样的场景,同步块 + wait/notify 或者 lock + signal/await 就是标配