虚假唤醒造成的后果,就是线程在争夺物品合法性操作时选择了 if 进行判断,出现了消费了没有的物品的现象。演示如下
我们假设一共有三个线程A、B、C
- A 是生产线程,靠condition1(见代码)控制
- B、C 是消费线程,靠condition2(见代码)控制
执行的代码如下所示
public class Demo {
public static void main(String[] args) {
Data data = new Data();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
data.increment();
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
data.decrement();
}
}, "B").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
data.decrement();
}
}, "C").start();
}
}
class Data {
private int num = 0;
Lock lock = new ReentrantLock();//非公平锁
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
//condition.await();//等待
//condition.signal();//唤醒
//+1
public void increment() {
lock.lock();//加锁
System.out.println(Thread.currentThread());
try {
if (num != 0) {//不用生产,等着
condition1.await();
}
num++;
System.out.println(Thread.currentThread().getName() + "=>" + num);
condition2.signal();//唤醒其他线程
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();//释放锁
}
}
//-1
public void decrement() {
lock.lock();//加锁
System.out.println(Thread.currentThread());
try {
// 虚假唤醒出现的位置
if (num == 0) {//等着,没有可消费的
condition2.await();
}
num--;
System.out.println(Thread.currentThread().getName() + "=>" + num);
condition1.signal();//唤醒其他线程,-1完毕
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
在我本机上执行的结果如下所示(部分),一共6次线程争夺(见下方解释)
Thread[A,5,main]
A=>1
Thread[C,5,main]
C=>0
Thread[C,5,main]
Thread[A,5,main]
A=>1
Thread[B,5,main]
B=>0
Thread[B,5,main]
C=>-1
通过ReentrantLock和Condition来实现生产者消费者,其执行过程如下
- A 抢到执行权(ABC抢夺),生产,唤醒阻塞线程(没有),释放锁
- C 抢到执行权(ABC抢夺),消费,唤醒阻塞线程(没有),释放锁
- C 抢到执行权(ABC抢夺),没有产品,阻塞
- A 抢到执行权(AB抢夺),生产,唤醒阻塞线程(C),释放锁
- B 抢到执行权(ABC抢夺),消费,唤醒阻塞线程(没有),释放锁
- C 抢到执行权(ABC抢夺),直接消费,出现虚拟唤醒(消费了没有的产品)
所以出现虚假唤醒是由于合法性判断的时候使用了 if,解决方法就是将 if 换成 while 在拿到执行权以后进行循环判断。
// 解决虚假消费
while (num == 0) {//等着,没有可消费的
condition2.await();
}