生产者消费者模式之虚假唤醒

生产者消费者模式&虚假唤醒

当出现虚假唤醒时,解决方法:将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();
        }
    }
}

posted on 2020-10-10 18:38  小小字节  阅读(310)  评论(0编辑  收藏  举报