【JUC】生产者消费者问题(包含虚假唤醒问题)

参考文章

【操作系统】生产者消费者问题_liushall-CSDN博客_生产者消费者问题操作系统

概念

生产者消费者问题(英语:Producer-consumer problem),也称有限缓冲问题(英语:Bounded-buffer problem),是一个多线程同步问题的经典案例。

 

可以理解为肯德基买炸鸡,

        生产者就相当于肯德基的后厨,生产炸鸡并放到前台

        消费者就相当于顾客,从前台获得炸鸡

        有限的缓冲区就相当于前台的保温柜里能放几只炸鸡

        但是生产炸鸡的时候顾客不能购买,购买炸鸡的时候后厨不能生产

注意事项:

        当缓冲区(前台)为空时,消费者(顾客)不能再进行消费

        当缓冲区(前台)为满时,生产者(后厨)不能再进行生产

        在线程进行生产或消费时,其余线程不能再进行生产或消费等操作,即保持线程的同步

通过传统synchronized锁来实现生产者消费者问题

实现生产者消费者问题的代码逻辑为

【JUC】生产者消费者问题(包含虚假唤醒问题)_第1张图片

 也既,先判断满不满足条件,不满足让出CPU,直到再次被唤醒,满足则执行业务操作,执行完成后唤醒其他线程

public class Demo02pc {
    public static void main(String[] args) {
        Production production = new Production();
        new Thread(()->{
            for (int i = 0; i < 100; i++) {
                production.increase();
            }
        },"A").start();

        new Thread(()->{
            for (int i = 0; i < 100; i++) {
                production.decrease();
            }
        },"B").start();
    }
}

class Production{
    private int product=0;
    public synchronized void increase(){
        if (product!=0){        //不满足条件
            //等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //业务操作
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        product++;          //完成生产的业务操作
        System.out.println("生产者为:"+Thread.currentThread().getName());
        //唤醒其他进程
        this.notifyAll();
    }
    public synchronized void decrease(){
        if(product==0){         //不满足条件
            //等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //业务
        product--;          //完成生产的业务操作
        System.out.println("消费者为:"+Thread.currentThread().getName());
        //唤醒
        this.notifyAll();
    }
}

运行后可以成功的模拟

虚拟唤醒问题

但是这只是一个生产者和一个消费者的问题,如果有多个生产者和消费者,用if就会出现问题

因为if只会判断一次

具体这个地方的理解方法,暂时还没有找到很好的解释,自己理解下来就是

        假如有两个生产者AC,两个消费者BD,当此时product为1,而且A和C同时进入增加状态,首先会判断product是否为1,这里product为1,所以线程AC都会进入等待状态,这时如果进入了一个消费者线程,将product减为0,并且唤醒所有等待的线程,因为是if判断,只会判断一次,所以两个生产者线程都会执行业务代码让product++,所以就会出现product为2的情况,这就叫做虚假唤醒

画个图可以更形象的说明

【JUC】生产者消费者问题(包含虚假唤醒问题)_第2张图片

一开始AC因为有产品,所以都进入了休眠状态等待没有产品的时候出来干活

【JUC】生产者消费者问题(包含虚假唤醒问题)_第3张图片

 这时候来了个线程D,把唯一的一个产品消耗了,然后唤醒了所有的线程A和C,这是A和C都通过了if判断,只不过暂时的“睡着了”,所以他们醒来以后就直接进行了业务操作,两次product++,那可不就是生成了两个产品嘛

【JUC】生产者消费者问题(包含虚假唤醒问题)_第4张图片

 那要怎么解决这个问题呢,其实很简单,在两个进程被唤醒后,再加一道循环不就行了嘛,所以我们这里就可以通过while循环来对进程进行判断,这样就算有两个生产者被唤醒,也只能有一个生产者能进入业务功能,另一个生产者则会通过while循环再次“睡去”

修改后的代码

package com.kuang;

public class Demo02pc {
    public static void main(String[] args) {
        Production production = new Production();
        new Thread(()->{
            for (int i = 0; i < 100; i++) {
                production.increase();
            }
        },"A").start();

        new Thread(()->{
            for (int i = 0; i < 100; i++) {
                production.decrease();
            }
        },"B").start();

        new Thread(()->{
            for (int i = 0; i < 100; i++) {
                production.increase();
            }
        },"C").start();

        new Thread(()->{
            for (int i = 0; i < 100; i++) {
                production.decrease();
            }
        },"D").start();
    }
}

class Production{
    private int product=0;
    public synchronized void increase(){
        if (product!=0){        //改为while循环
            //等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //业务操作
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        product++;          //完成生产的业务操作
        System.out.println("生产者为:"+Thread.currentThread().getName()+"当前产品数量为:"+product);
        //唤醒其他进程
        this.notifyAll();
    }
    public synchronized void decrease(){
        while(product==0){         //改为while循环
            //等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //业务
        product--;          //完成生产的业务操作
        System.out.println("消费者为:"+Thread.currentThread().getName()+"当前产品数量为:"+product);
        //唤醒
        this.notifyAll();
    }
}

Lock锁解决生产者消费者问题

我们通过Lock接口实现的ReentrantLock中有一个方法可以创建一个Condition的对象

【JUC】生产者消费者问题(包含虚假唤醒问题)_第5张图片

 我们进入Condition类,可以看到有一个await方法和signalAll方法

【JUC】生产者消费者问题(包含虚假唤醒问题)_第6张图片

 这两个方法就相当于synchronized锁中的wait和notifyAll方法

【JUC】生产者消费者问题(包含虚假唤醒问题)_第7张图片

 修改后的代码为

package com.kuang;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Demo02pc {
    public static void main(String[] args) {
        Production production = new Production();
        new Thread(()->{
            for (int i = 0; i < 100; i++) {
                production.increase();
            }
        },"A").start();

        new Thread(()->{
            for (int i = 0; i < 100; i++) {
                production.decrease();
            }
        },"B").start();

        new Thread(()->{
            for (int i = 0; i < 100; i++) {
                production.increase();
            }
        },"C").start();

        new Thread(()->{
            for (int i = 0; i < 100; i++) {
                production.decrease();
            }
        },"D").start();
    }
}

class Production{
    private int product=0;
    private Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();
    public void increase(){
        lock.lock();
        try {
            if (product!=0){        //改为while循环
                //等待
                condition.await();
            }
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            product++;          //完成生产的业务操作
            System.out.println("生产者为:"+Thread.currentThread().getName()+"当前产品数量为:"+product);
            //唤醒其他进程
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
        //业务操作

    }
    public void decrease(){
        lock.lock();
        try {
            while(product==0){         //改为while循环
                //等待
                try {
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //业务
            product--;          //完成生产的业务操作
            System.out.println("消费者为:"+Thread.currentThread().getName()+"当前产品数量为:"+product);
            //唤醒
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

通过Condition实现精准唤醒

我们知道signalAll是唤醒所有的沉睡进程,signal是随机唤醒队列中第一个进程,那我们有什么班法唤醒指定的进程呢?

        这就需要我们利用Condition来实现精准唤醒

我们可以在一把锁中创建4个Condition对象,然后通过一个计数器判断该唤醒那个进程

代码如下

public class Demo03 {
    public static void main(String[] args) {
        Data data = new Data();
        new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                data.Print1();
            }

        }, "A").start();

        new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                data.Print2();
            }

        }, "B").start();

        new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                data.Print3();
            }
        }, "C").start();
    }
}
class Data{
    private int product=0;
    private Lock lock = new ReentrantLock();
    Condition condition1 = lock.newCondition();
    Condition condition2 = lock.newCondition();
    Condition condition3 = lock.newCondition();
    private int number=1;
    public void Print1(){
        lock.lock();

        try {
            while(number!=1){
                //等待
                try {
                    condition1.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("AAAAAAAAAA");
            number=2;
            condition2.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void Print2(){
        lock.lock();
        try {
            while(number!=2){
                //等待
                try {
                    condition2.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            number=3;
            System.out.println("BBBBBBBBBBBB");
            condition3.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void Print3(){
        lock.lock();
        try {
            while(number!=3){
                //等待
                try {
                    condition3.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            number=1;
            System.out.println("CCCCCCCCCC");
            condition1.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

你可能感兴趣的:(多线程JUC,java,rabbitmq,rpc,juc)