生产者消费者模式之虚假唤醒
生产者消费者模式&虚假唤醒
当出现虚假唤醒时,解决方法:将if
判断改为while
循环。
在弄懂虚假唤醒之前,先了解两个定义:
1、重入:“重入”意味着获取锁的操作的粒度是“线程”,而不是“调用”;
2、线程的生命周期&线程状态图:
当我们只有两个线程的时候,即"A"(生产者)和"B"(消费者)。整个模式不会出现问题,当增加两个线程"C"(生产者)和"D"(消费者)后,结果就变成了这样:
造成这种情况的过程如下图(第一行的应该是释放锁后,AC获得锁则会...当时笔误😏):
用if判断的话,唤醒后线程会从wait之后的代码开始运行,但是不会重新判断if条件,直接继续运行if代码块之后的代码,而如果使用while的话,也会从wait之后的代码运行,但是唤醒后会重新判断循环条件,如果不成立再执行while代码块之后的代码块,成立的话继续wait。
这就是所谓的”虚假唤醒“,即:当只有一份产品的时候,唤醒了多余的消费者,使程序产生不必要的问题。那应该怎么修改呢?官方文档给了我们答案:
将同步方法下判断等待语句中的 if 换成 while 后就正常多了。
修改后的代码如下:
public class TestDemo {
public static void main(String[] args) {
Data data = new Data();
new Thread(
() -> {
for (int i = 0; i < 10; i++) {
try {
data.producer();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "producerA").start();
new Thread(
() -> {
for (int i = 0; i < 10; i++) {
try {
data.cosumer();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "cosumerB").start();
new Thread(
() -> {
for (int i = 0; i < 10; i++) {
try {
data.producer();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "producerC").start();
new Thread(
() -> {
for (int i = 0; i < 10; i++) {
try {
data.cosumer();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "cosumerD").start();
}
}
/*
* 1、判断等待
* 2、业务
* 3、唤醒
* */
class Data {
private int number = 0;
//0
public synchronized void producer() throws InterruptedException {
if (number != 0)
this.wait();
number++;
System.out.println(Thread.currentThread().getName() + "-->" + number);
this.notifyAll();
}
//1
public synchronized void cosumer() throws InterruptedException {
if (number == 0)
this.wait();
number--;
System.out.println(Thread.currentThread().getName() + "-->" + number);
this.notifyAll();
}
}
现在还有一个问题就是,这样的生产者消费者模式不连贯,我们能不能让它遵循:A
-->B
-->C
-->D
的顺序来进行作业呢?这里就要引入Lock
类下的Condition 精准的通知和唤醒线程
。
简单地说就是关键字synchronized
的功能它都有,同时增加了指定线程执行顺序的功能:
下面将生产者消费者模式由synchronized
模式改成Lock_Condition
模式
public class TestDemo {
public static void main(String[] args) {
Data data = new Data();
new Thread(
() -> {
for (int i = 0; i < 10; i++) {
try {
data.producerA();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "producerA").start();
new Thread(
() -> {
for (int i = 0; i < 10; i++) {
try {
data.cosumerB();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "cosumerB").start();
new Thread(
() -> {
for (int i = 0; i < 10; i++) {
try {
data.producerC();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "producerC").start();
new Thread(
() -> {
for (int i = 0; i < 10; i++) {
try {
data.cosumerD();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "cosumerD").start();
}
}
/*
* 1、判断等待
* 2、业务
* 3、唤醒
* */
class Data {
private int number = 0;
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
private Condition condition4 = lock.newCondition();
//0
public void producerA() throws InterruptedException {
lock.lock();
try {
while (number != 0)
condition1.await();
System.out.println(Thread.currentThread().getName() + "-->" + number);
number++;
condition2.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
//1
public void cosumerB() throws InterruptedException {
lock.lock();
try {
while (number == 0)
condition2.await();
System.out.println(Thread.currentThread().getName() + "-->" + number);
number--;
condition3.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
//0
public void producerC() throws InterruptedException {
lock.lock();
try {
while (number != 0)
condition3.await();
System.out.println(Thread.currentThread().getName() + "-->" + number);
number++;
condition4.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
//1
public void cosumerD() throws InterruptedException {
lock.lock();
try {
while (number == 0)
condition4.await();
System.out.println(Thread.currentThread().getName() + "-->" + number);
number--;
condition1.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}