多线程&JUC:等待唤醒机制(生产者消费者模式)

👨‍🎓作者简介:一位大四、研0学生,正在努力准备大四暑假的实习
🌌上期文章:多线程&JUC:解决线程安全问题——synchronized同步代码块、Lock锁
📚订阅专栏:多线程&JUC
希望文章对你们有所帮助

等待唤醒机制

等待唤醒机制也叫做生产者消费者模式,打破了以前线程间执行的随机性,生产者消费者模式能够使得线程之间是轮流运行的。是一个非常经典的多线程协作的模式。
对于两条线程,其中一条为生产者,另一条为消费者,大家都是学习过操作系统的,原理多少还是记得一些的。

对于等待唤醒机制,其只有2种情况:

1、消费者等待:若没有可以被消费者消费的数据,那么消费者就是进入wait状态,这时候生产者就可以抢占CPU生产数据,接着notify(唤醒)消费者
2、生产者等待:若已经有数据供给消费者消费,则生产者进入wait状态,消费者抢占CPU消费数据,接着notify(唤醒)生产者

在这其中可能会涉及到的方法:

方法名称 说明
void wait() 当前线程等待,直到被其他线程唤醒
void notify() 随机唤醒单个线程
void notifyAll() 唤醒所有线程

等待唤醒机制的实现

消费者代码实现

消费者和生产者中间有一个控制他们执行相应操作的核心,视为Controller,记录一些状态变量和锁对象:

public class Controller {
   
    /**
     * 控制消费者和生产者的执行
     */
    //表示是否有数据 0:没有 1:有
    public static int flag = 0;

    //消费者最多可以消费的数据量
    public static int count = 10;

    //锁对象
    public static Object lock = new Object();
}

接着实现消费者的逻辑:

public class Consumer extends Thread{
   
    @Override
    public void run() {
   
        while(true){
   
            synchronized (Controller.lock) {
   
                if(Controller.count == 0){
   
                    //消费者已经消费量了10次,退出
                    break;
                }else{
   
                    //先判断有无可以消费的数据
                    if(Controller.flag == 0) {
   
                        //若无,等待
                        //用lock调用wait方法,使得当前线程与锁进行绑定,之后唤醒就唤醒这些被绑定了的线程
                        try {
   
                            Controller.lock.wait();
                        } catch (InterruptedException e) {
   
                            throw new RuntimeException(e);
                        }
                    }else{
   
                        //若有,消费
                        System.out.println("正在消费,还可以消费" + --Controller.count + "个");
                        //消费完后唤醒生产者,唤醒绑定在这把锁上的所有线程
                        Controller.lock.notifyAll();
                        //修改控制中心的状态
                        Controller.flag = 0;
                    }
                }
            }
        }
    }
}

生产者代码实现

public class Producer extends Thread{
   
    @Override
    public void run() {
   
        while (true){
   
            synchronized (Controller.lock){
   
                if(Controller.count == 0){
   
                    break;
                }else{
   
                    if(Controller.flag == 1){
   
                        //已经有供给消费者进行消费的数据
                        try {
   
                            Controller.lock.wait();
                        } catch (InterruptedException e) {
   
                            throw new RuntimeException(e);
                        }
                    }else{
   
                        System.out.println("成功生产");
                        Controller.lock.notifyAll();
                        Controller.flag = 1;
                    }
                }
            }
        }
    }
}

最后编写测试类代码验证:

public class ThreadDemo {
   
    public static void main(String[] args) {
   
        //创建线程对象
        Producer producer = new Producer();
        Consumer consumer = new Consumer();
        //给线程设置名字
        producer.setName("生产者");
        consumer.setName("消费者");
        //开启线程
        producer.start();
        consumer.start();
    }
}

阻塞队列实现等待唤醒机制

何为阻塞队列?其实就是连接生产者和消费者的一个队列,管理着数据,分别供消费者take和生产者的put,如果put不进去或者take不出,则说明队列满了或者空了,这时候就会进入阻塞状态。

阻塞队列BlockingQueue本身实现了Iterable、Collection、Queue的接口,无法直接实例化,但是其具有2个实现类:

1、ArrayBlockingQueue:底层为数组,有界
2、LinkedBlockingQueue:底层为链表,无界(不是真正的无界,最大为int的最大范围,只是无须指定范围)

利用阻塞队列来实现是很便捷的,因为我们可以查看put和take方法的底层,可以发现这两个方法是自带锁的,所以我们在实现生产者和消费者的时候无须自己上锁,否则反而会容易因为锁的嵌套而发生死锁。
在这里插入图片描述
在这里插入图片描述
生产者代码:

public class Producer extends Thread{
   

    ArrayBlockingQueue<String> queue;

    public Producer(ArrayBlockingQueue<String> queue) {
   
        this.queue = queue;
    }

    @Override
    public void run() {
   
        while (true) {
   
            //直接不断的把数据放进阻塞队列,如果满了它自己会阻塞
            try {
   
                queue.put("数据");
                System.out.println("消费者生产了一个数据到阻塞队列");
            } catch (InterruptedException e) {
   
                throw new RuntimeException(e);
            }
        }
    }
}

消费者代码:

public class Consumer extends Thread{
   

    ArrayBlockingQueue<String> queue;

    public Consumer(ArrayBlockingQueue<String> queue) {
   
        this.queue = queue;
    }

    @Override
    public void run() {
   
        while (true){
   
            try {
   
                String take = queue.take();
                System.out.println(take);
            } catch (InterruptedException e) {
   
                throw new RuntimeException(e);
            }
        }
    }
}

测试类:

public class ThreadDemo {
   
    /**
     * 使用阻塞队列实现等待唤醒机制,要保证生产者和消费者用的是同一个阻塞队列
     */
    public static void main(String[] args) {
   
        //创建一个可以存放1个数据的阻塞队列
        ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);
        //创建生产者和消费者对象,并把阻塞队列传递过去,使得它们使用同一个阻塞队列
        Producer producer = new Producer(queue);
        Consumer consumer = new Consumer(queue);

        producer.setName("生产者");
        consumer.setName("消费者");

        producer.start();
        consumer.start();
    }
}

在这里插入图片描述
最后显示可能会重复打印数据,这是因为输出的语句没有放在锁里面,锁可以执行的put和take已经写死了,但是并不影响我们实际数据的并发安全性,只是不方便我们的观察罢了。

至此,阻塞队列实现等待唤醒机制的demo已经跑通了,阻塞队列底层的执行实际上是异步的,可以解决在实际生产环境中的超卖问题,具体可以看我之前的文章:
Redis:原理速成+项目实战——Redis实战9(秒杀优化)

当然,主流的方法还是使用消息队列RabbitMQ或Kafka,这个大家可以自行去了解。

相关推荐

最近更新

  1. TCP协议是安全的吗?

    2024-02-09 14:52:01       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-02-09 14:52:01       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-02-09 14:52:01       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-02-09 14:52:01       20 阅读

热门阅读

  1. Python(22)正则表达式中的“限定符”

    2024-02-09 14:52:01       30 阅读
  2. C语言之扫描字符串

    2024-02-09 14:52:01       33 阅读
  3. Kubernetes的有状态应用示例:ZooKeeper

    2024-02-09 14:52:01       21 阅读
  4. JVM体系

    2024-02-09 14:52:01       31 阅读
  5. c语言_实现类class的功能 实例

    2024-02-09 14:52:01       31 阅读
  6. 贪心_分类讨论_边界问题_1921_C. Sending Messages

    2024-02-09 14:52:01       27 阅读
  7. c实现链表

    2024-02-09 14:52:01       28 阅读
  8. deepin20.9安装及配置

    2024-02-09 14:52:01       28 阅读
  9. 高精度加法 取余 分类讨论 AcWing 791. 高精度加法

    2024-02-09 14:52:01       32 阅读
  10. 【LeetCode每日一题】1122. 数组的相对排序

    2024-02-09 14:52:01       31 阅读
  11. LeetCode639. Decode Ways II——动态规划

    2024-02-09 14:52:01       24 阅读
  12. C++ .h文件类的调用

    2024-02-09 14:52:01       28 阅读
  13. 机器学习原理到Python代码实现之PolynomialRegression

    2024-02-09 14:52:01       28 阅读