虚假唤醒是因为 wait()在哪里停,被唤醒时就从哪开始 造成的。
1.先模拟生产者消费者问题:
AA 生产,BB消费
class Stuff{
private int num = 0;
//+1
public synchronized void increase() throws InterruptedException {
//如果数量大于0,则不生产
//判断
if(num > 0){
this.wait();
}
//干活
System.out.println(Thread.currentThread().getName() + "生产,剩余" + ++num);
//通知
this.notifyAll();
}
//-1
public synchronized void decrease() throws InterruptedException {
//如果数量小于1,则不消费
//判断
if(num < 1){
this.wait();
}
//干活
System.out.println(Thread.currentThread().getName() + "消费,剩余" + --num);
//通知
this.notifyAll();
}
}
public class FalseWakeup {
public static void main(String[] args) {
Stuff stuff = new Stuff();
//造俩线程,模拟生产消费
new Thread(()->{
try {
for (int i = 0; i < 10; i++) {
stuff.increase();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
},"AA").start();
new Thread(()->{
try {
for (int i = 0; i < 10; i++) {
stuff.decrease();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
},"BB").start();
}
}
输出:
两个线程并不会出现虚假唤醒,
但是现在用四个线程:加入 CC 线程 生产,DD线程消费
//新增线程:CC,DD
new Thread(()->{
try {
for (int i = 0; i < 10; i++) {
stuff.decrease();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
},"CC").start();
new Thread(()->{
try {
for (int i = 0; i < 10; i++) {
stuff.decrease();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
},"DD").start();
输出:
然后就会发现出现了负数的情况。
原因:
wait()被挂起时,已经进行了判断,被唤醒时也会从那开始,初始if()判断就不管用了,就存在了虚假唤醒。
查开发者文档时,关于wait()方法也有:
像在一个参数版本中,中断和虚假唤醒是可能的,并且该方法应该始终在循环中使用:
synchronized (obj) {
while ()
obj.wait();
… // Perform action appropriate to condition
}
解决:
把判断放在while循环中:
输出:
使用**lock**时避免虚假唤醒问题: 代码改成lock接口的:
class StuffLock {
private int num = 0;
private ReentrantLock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
//+1生产
public void increase() throws InterruptedException {
lock.lock();
try {
//判断 大于0时,停止生产
while (num > 0) {
condition.await();
}
//干活
System.out.println(Thread.currentThread().getName() + "生产,剩余" + ++num);
//通知 通知消费者可以来消费了
condition.signalAll();
} finally {
lock.unlock();
}
}
//-1 消费
public void decrease() throws InterruptedException {
//相当于synchronized
lock.lock();
try {
//判断 小于1时,停止消费
while (num < 1) {
//相当于wait()
condition.await();
}
//干活
System.out.println(Thread.currentThread().getName() + "消费,剩余" + --num);
//通知 通知生产者开始生产
condition.signalAll();
} finally {
lock.unlock();
}
}
}
public class FalseWakeupLock {
public static void main(String[] args) {
StuffLock stuff = new StuffLock();
//创建四个线程AA、BB、CC、DD来模拟生产者和消费者,AA、CC是生产者
new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
stuff.increase();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "AA").start();
new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
stuff.decrease();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "BB").start();
new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
stuff.increase();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "CC").start();
new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
stuff.decrease();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "DD").start();
}
}
输出结果:
使用Lock接口时,要返回Condition用于这种用途实例Lock实例,在这个实例中调用await()方法将线程挂起,再调用signalAll() 唤醒所有挂起的线程。
总结一下,虚拟唤醒是因为线程调用wait()方法挂起时,在哪里值行的挂起操作,被唤醒时就会从该处继续执行。因为只有if()依次判断,所以在被唤醒之后,原num值就有可能已经改变,判断相当于未生效。