while(count<=0)
wait()
count--
这里面有问题。什么问题呢?
生产者是两个步骤:
- count+1;
- notify();
消费者也是两个步骤:
- 检查count值;
- 睡眠或者减一;
如果这些步骤混在一起会怎样呢?
比如说,初始的时候count等于0,这个时候消费者检查count的值,发现count小于等于0的条件成立;就在这个时候,发生了上下文切换,生产者进来了,噼噼啪啪一顿操作,把两个步骤都执行完了,也就是发出了通知,准备唤醒一个线程。这个时候消费者刚决定睡觉,还没睡呢,所以这个通知就会被丢掉。紧接着,消费者就睡过去了,消费者成睡美人了。
这就是所谓的lost wake up问题。
尝试解决
问题的根源在于,消费者在检查count到调用wait()之间,count就可能被改掉了。
常见的解决方式是加锁,让消费者和生产者竞争一把锁,竞争到了的,才能够修改count的值。
我这里将两者的两个操作都放进去了同步块中,于是生产者的代码是:
tryLock()
count+1
notify()
releaseLock()
消费者的代码是:
tryLock()
while(count <= 0)
wait()
count-1
releaseLock()
但是这样改后依旧会出现lost wake up问题,而且和无锁的表现是一样的。
最终解决
为了避免出现这种lost wake up问题,在这种模型之下,应该将我们的代码放进去的同步块中。
Java强制我们的wait()/notify()调用必须要在一个同步块中,就是不想让我们在不经意间出现这种lost wake up问题。
不仅仅是这两个方法,包括java.util.concurrent.locks.Condition的await()/signal()也必须要在同步块中:
private ReentrantLock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
@Test
public void test2() {
try {
condition.signal();
} catch (Exception e) {
e.printStackTrace();
}
}
准确的来说,即便是我们自己在实现自己的锁机制的时候,也应该要确保类似于wait()和notify()这种调用,要在同步块内,防止使用者出现lost wake up问题。
Java的这种检测是很严格的。它要求的是,一定要处于锁对象的同步块中。举例来说:
private Object obj = new Object();
private Object anotherObj = new Object();
@Test
public void test3() {
synchronized (obj) {
try {
anotherObj.notify();
} catch (Exception e) {
e.printStackTrace();
}
}
}
这样也是没什么用的。一样出现IllegalMonitorStateException。
}
}
[外链图片转存中...(img-emflc5lN-1630037538998)]
这样也是没什么用的。一样出现IllegalMonitorStateException。