当前位置: 首页 > article >正文

阻塞队列:原理、应用及实现

阻塞队列:原理、应用及实现

    • 什么是阻塞队列
    • 以生产消费者模型形象地理解阻塞队列
    • 阻塞队列实现生产消费者模型
    • 模拟实现阻塞队列实现生产消费者模型

什么是阻塞队列

阻塞队列是一种特殊且实用的队列数据结构,它同样遵循 “先进先出” 的原则。与普通队列不同的是,阻塞队列是线程安全的,这使得它在多线程编程中扮演着重要角色。

阻塞队列具有以下两个关键特性:

  • 当队列已满时,如果有线程尝试将元素入队列,该线程将会被阻塞,直到有其他线程从队列中取走元素,使得队列腾出空间。
  • 当队列为空时,若有线程试图从队列中出队列,此线程也会被阻塞,直至有其他线程向队列中插入新的元素。

阻塞队列的一个经典应用场景便是 “生产者消费者模型”。这种模型在软件开发中被广泛使用,能够有效地协调不同线程之间的工作,提高程序的性能和稳定性。

以生产消费者模型形象地理解阻塞队列

为了更直观地理解阻塞队列的工作原理,我们以一个具体的生产消费者场景为例。假设有四个生产者线程和一个消费者线程:

  • 每个生产者线程每单位时间能够生产 4 个面包。
  • 消费者线程每单位时间只能取走 1 个面包。
    在这里插入图片描述

在这种情况下,由于生产者的生产速度远快于消费者的消费速度,每单位时间会有 3 个面包进入阻塞队列,等待消费者线程来取。并且在这个过程中,生产者线程不会停止生产。
在这里插入图片描述

随着时间的推移,当阻塞队列被填满,没有空位时,生产者线程就会因为无法继续入队列而停止生产,直到消费者线程从队列中取走一些面包,腾出空间。
在这里插入图片描述

阻塞队列实现生产消费者模型

在 Java 中,BlockingQueue接口及其实现类为我们提供了便捷的方式来实现生产消费者模型。以下是 BlockingQueue 的构造方法示例图:

在这里插入图片描述

下面通过一段 Java 代码来具体实现生产消费者模型(为了便于观察和理解,我们创建了一个固定容量的阻塞队列):

import java.util.Random;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.LinkedBlockingDeque;

public class Main {
    public static void main(String[] args) {
        Random random = new Random();
        // 首先创建一个阻塞队列,容量为 10
        BlockingDeque<Integer> blockingDeque = new LinkedBlockingDeque<>(10);

        // 创建生产者线程
        Thread t1 = new Thread(() -> {
            while (true) {
                try {
                    int value = random.nextInt(100);
                    System.out.println("生产元素:" + value);
                    blockingDeque.put(value);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });

        // 创建消费者线程
        Thread t2 = new Thread(() -> {
            while (true) {
                try {
                    System.out.println("消费元素:" + blockingDeque.take());
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });

        t1.start();
        t2.start();
    }
}

通过上述代码,生产者线程不断生产随机整数并放入阻塞队列,而消费者线程则从队列中取出元素并打印,同时每隔 1 秒消费一次,模拟实际的消费速度。
在这里插入图片描述

模拟实现阻塞队列实现生产消费者模型

除了使用 Java 提供的现成阻塞队列实现类,我们还可以自己模拟实现一个固定容量的阻塞队列。在这个实现过程中,需要重点关注以下三个方面:

  1. 确保线程安全,避免多个线程同时访问队列时出现数据不一致的问题。
  2. 当队列为空时,消费者线程需要阻塞等待,直到队列中有新的元素可供消费。
  3. 当队列为满时,生产者线程需要阻塞等待,直到队列中有元素被消费,腾出空间。

以下是模拟实现的代码:

public class MyBlockqueue {
    // 队列的最大容量
    int max = 10;
    // 创建一个固定容量的数组来存储队列元素
    int[] arr;
    // 记录队列中已有元素的个数
    int num;
    // 队列头元素的下标,遵循先进先出原则
    int first;
    // 队列尾元素的下标,遵循后进后出原则
    int end;

    public MyBlockqueue(int max) {
        this.max = max;
        arr = new int[max];
    }

    // 模拟实现入队列操作
    public void put(int x) throws InterruptedException {
        // 检查队列是否已满
        while (num >= max) {
            // 如果已满,当前线程进入阻塞状态
            synchronized (this) {
                wait();
            }
        }

        // 队列未满,添加元素
        synchronized (this) {
            arr[end] = x;
            end = (end + 1) % max;
            num++;
            // 唤醒其他可能在等待的线程
            notify();
        }
    }

    // 模拟实现出队列操作
    public Integer take() throws InterruptedException {
        // 检查队列是否为空
        while (num == 0) {
            // 如果为空,当前线程进入阻塞状态
            synchronized (this) {
                wait();
            }
        }

        // 队列不为空,取出元素
        synchronized (this) {
            int tmp = arr[first];
            first = (first + 1) % max;
            num--;
            // 唤醒其他可能在等待的线程
            notify();
            return tmp;
        }
    }
}

接下来,我们使用这个自定义的阻塞队列来实现生产消费者模型:

import java.util.Random;

public class Main {
    public static void main(String[] args) {
        Random random = new Random();
        // 创建一个自定义的阻塞队列,容量为 10
        MyBlockqueue blockingDeque = new MyBlockqueue(10);

        // 创建生产者线程
        Thread t1 = new Thread(() -> {
            while (true) {
                try {
                    int value = random.nextInt(100);
                    System.out.println("生产元素:" + value);
                    blockingDeque.put(value);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });

        // 创建消费者线程
        Thread t2 = new Thread(() -> {
            while (true) {
                try {
                    System.out.println("消费元素:" + blockingDeque.take());
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });

        t1.start();
        t2.start();
    }
}

在这里插入图片描述

通过上述代码,我们成功地模拟实现了一个阻塞队列,并基于它构建了生产消费者模型,帮助我们更深入地理解阻塞队列的工作原理和应用场景。

希望这篇博客能让你对阻塞队列有更全面、深入的认识!


http://www.kler.cn/a/598349.html

相关文章:

  • 密集球光照贴图生成
  • 什么情况下spring的事务会失效
  • 【WAF 安全实践】
  • 基于TCP/QT/C++的网络调试助手测试报告
  • 手机扫描仪 含PDF转word功能+OCR识别110种语言
  • 01_JavaScript
  • 深度学习之防止过拟合
  • 307 Temporary Redirect 解决办法 httpx
  • nebula graph传统使用Docker进行项目发版
  • 【笔记分享】NCS/Zephyr USB MSC实现方法介绍
  • 使用 Python 和 python-pptx 构建 Markdown 到 PowerPoint 转换器
  • 153页PPT华为市场管理全景:战略、逻辑、阶段与工具深度解析
  • 从扩展黎曼泽塔函数构造物质和时空的结构-3
  • 高性能缓存:使用 Redis 和本地内存缓存实战示例
  • Altium Designer数模电学习笔记
  • 常用数据库远程连接工具全解析:从入门到高效管理
  • 基于springboot的校园资料分享平台(048)
  • 设计模式之工厂模式的优缺点
  • Qt中通过QLabel实时显示图像
  • 【AI大模型】搭建本地大模型GPT-NeoX:详细步骤及常见问题处理