阻塞队列的原理及应用

这篇具有很好参考价值的文章主要介绍了阻塞队列的原理及应用。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

阻塞队列是一种常用的并发编程工具,它能够在多线程环境下提供一种安全而高效的数据传输机制。本文将介绍阻塞队列的原理和使用场景,并通过实例演示其在多线程编程中的应用。

一、什么是阻塞队列

阻塞队列是一种特殊的队列,它具有以下几个特点:

  1. 阻塞特性:当队列为空时,从队列中获取元素的操作将会被阻塞,直到队列中有新的元素被添加;当队列已满时,向队列中添加元素的操作将会被阻塞,直到队列中有空的位置,这就是等待唤醒机制。
  2. 线程安全:阻塞队列内部通过锁或其他同步机制来保证多线程环境下的数据一致性。
  3. 有界性:阻塞队列可以设置容量上限,当队列满时,后续的元素将无法添加。
  4. 公平性:阻塞队列可以选择公平或非公平的策略来决定线程的获取顺序。公平队列会按照线程的请求顺序进行处理(线程按先来后到顺序排队获取元素),而非公平队列则允许新的线程插队执行(线程竞争)。比如:SynchronousQueue。

阻塞队列常用于解决生产者-消费者问题,它能够有效地衔接生产者和消费者之间的速度差异,提供一种协调和安全的数据交互方式。
阻塞队列底层一般采用数组和链表这两种数据结构存储元素,ArrayBlockingQueue和PriorityBlockingQueue底层都是采用数组存储的,但是ArrayBlockingQueue是必须指定数组大小,不能扩容,而PriorityBlockingQueue可以进行动态扩容(扩容的最大长度也是Integer.MAX_VALUE),LinkedBlockingQueue底层是链表结构存储,虽然是链表,但是也有长度限制,默认是Integer.MAX_VALUE,一般认为的无界阻塞队列,其实最大的队列长度也就是Integer.MAX_VALUE。

二、阻塞队列的核心方法

  • 添加
方法 描述 是否阻塞
add方法 往队列尾部添加元素,内部是调用offer方法
put方法 往队列尾部添加元素,如果队列已满,则阻塞等待
offer方法 往队列尾部添加元素,如果队列已满,则返回false,不会阻塞
  • 获取
方法 描述 是否阻塞
take方法 take方法:移除并返回队列头部的元素,如果队列为空,则阻塞等待
poll方法 移除并返回队列头部的元素,如果队列为空,则返回null,不会阻塞
peek方法 返回队列头部的元素(不移除),如果队列为空,则返回null,不会阻塞

三、常见的阻塞队列实现

阻塞队列的原理及应用
通过图中可以看到,BlockingQueue集成了Queue接口的功能,有多种子类实现,常用的如下:

  1. ArrayBlockingQueue:基于数组实现的有界阻塞队列,它的容量在创建时指定,并且不能动态扩展。
  2. LinkedBlockingQueue:基于链表实现的有界阻塞队列,链表的长度可以通过构造函数显式指定,如果使用默认的构造函数,则默认大小是Integer.MAX_VALUE。
  3. PriorityBlockingQueue:基于优先级堆排序实现的阻塞队列(可扩容),元素按照优先级顺序进行排序。
  4. SynchronousQueue:不存储元素的阻塞队列,每个插入操作都必须等待一个相应的删除操作,反之亦然。

四、阻塞队列的原理

常用的阻塞队列,比如:ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue底层都是采用ReentrantLock锁来实现线程的互斥,而ReentrantLock底层采用了AQS框架实现线程队列的同步,线程的阻塞是调用LockSupport.park实现,唤醒是调用LockSupport.unpark实现,具体可以看我之前的文章,SynchronousQueue底层虽然没有用AQS框架,但也用的是LockSupport实现线程的阻塞与唤醒。
一文读懂LockSupport
AQS源码分析
阻塞队列的原理可以通过两个关键组件来解释:锁和条件变量。

阻塞队列使用锁来保护共享资源,控制线程的互斥访问。在队列为空或已满时,线程需要等待相应的条件满足才能继续执行。

  • 条件变量

条件变量是锁的一个补充,在某些特定的条件下,线程会进入等待状态。当条件满足时,其他线程会通过调用条件变量的唤醒方法(比如signal()或signalAll())来通知等待的线程进行下一步操作。
当一个线程试图从空的阻塞队列中获取元素时,它会获取队列的锁,并检查队列是否为空。如果为空,这个线程将进入等待状态,直到其他线程向队列中插入元素并通过条件变量唤醒它。当一个线程试图向已满的阻塞队列插入元素时,它会获取队列的锁,并检查队列是否已满。如果已满,这个线程将进入等待状态,直到其他线程从队列中获取元素并通过条件变量唤醒它。
接下来我们看下阻塞队列的获取元素和插入元素的核心代码:
ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue的带阻塞的插入和获取方法都是基于ReentrantLock锁+条件变量的等待和通知来实现的。
主要看看ArrayBlockingQueue带阻塞的插入和获取元素的主要方法吧。

/**
 * 插入元素,带阻塞
 */
public void put(E e) throws InterruptedException {
    checkNotNull(e);
    // 这里使用的是ReentrantLock锁
    final ReentrantLock lock = this.lock;
    // 获取锁并支持响应中断,注意:获取锁的过程中不响应中断,是在获取到锁后根据当前线程的中断标识来处理。
    lock.lockInterruptibly();
    try {
        // 元素大小等于数组长度时阻塞,说明放满了,生产者需要暂停,阻塞在条件变量上,等待被唤醒
        while (count == items.length)
            notFull.await();
        // 放入元素到数组指定的下标处
        enqueue(e);
    } finally {
        // 释放锁
        lock.unlock();
    }
}

/**
 * 插入元素,唤醒等待获取元素的线程
 */
private void enqueue(E x) {
    // assert lock.getHoldCount() == 1;
    // assert items[putIndex] == null;
    final Object[] items = this.items;
    items[putIndex] = x;
    if (++putIndex == items.length)
        putIndex = 0;
    count++;
	// 放入元素后,通知消费线程继续获取元素
    notEmpty.signal();
}

/**
 * 获取元素,带阻塞
 */
public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        // 数组无元素时阻塞,阻塞在条件变量上,等待被唤醒
        // 元素大小等于0时阻塞,说明数组被取空了,消费者需要暂停,阻塞在条件变量上,等待被唤醒
        while (count == 0)
            notEmpty.await();
        // 移除元素并返回
        return dequeue();
    } finally {
        lock.unlock();
    }
}



/**
 * 移除元素并返回
 */
private E dequeue() {
    // assert lock.getHoldCount() == 1;
    // assert items[takeIndex] != null;
    final Object[] items = this.items;
    @SuppressWarnings("unchecked")
    E x = (E) items[takeIndex];
    items[takeIndex] = null;
    // 数组时循环使用的,取元素的index到达数组长度时,下次需要从第0个位置
    if (++takeIndex == items.length)
        takeIndex = 0;
    count--;
    if (itrs != null)
        itrs.elementDequeued();
    // 移除元素后,通知消费者线程可以继续放入元素
    notFull.signal();
    return x;
}

SynchronousQueue不存储元素,插入和删除是配套使用的,它的插入和删除有公平和非公平之分,公平是通过内部类TransferQueue实现的,非公平是通过TransferStack实现的,具体可以看transfer方法,最终会调用LockSupport.park实现线程阻塞,LockSupport.unpark实现线程继续执行,这个就不贴代码了。

五、阻塞队列的使用场景

  1. 生产者-消费者模型:阻塞队列能够很好地平衡生产者和消费者之间的速度差异,既能保护消费者不会消费到空数据,也能保护生产者不会造成队列溢出,能够有效地解耦生产者和消费者,提高系统的稳定性和吞吐量。
  2. 线程池:在线程池中,阻塞队列可以作为任务缓冲区,将待执行的任务放入队列中,由线程池中的工作线程按照一定的策略进行执行。
  3. 同步工具:阻塞队列还可以作为一种同步工具,在多线程环境下实现线程之间的协作。
  4. 数据缓冲:阻塞队列可以用作数据缓冲区,当生产者的速度大于消费者的速度时,数据可以先存储在队列中,等待消费者处理
  5. 事件驱动编程:阻塞队列可以用于事件驱动的编程模型,当事件发生时,将事件对象放入队列中,由消费者进行处理

六、阻塞队列的使用

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.PriorityBlockingQueue;

public class BlockingQueueExample {
    public static void main(String[] args) {
        // 创建一个容量为10的ArrayBlockingQueue
        BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10);
//        BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(10);
//        BlockingQueue<Integer> queue = new PriorityBlockingQueue<>(10);
        // 创建生产者线程
        Thread producerThread = new Thread(() -> {
            try {
                for (int i = 0; i <= 5; i++) {
                    // 将数据放入队列
                    queue.put(i);
                    System.out.println(Thread.currentThread().getName() + "Produced: " + i);
                    Thread.sleep(500);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        // 创建消费者线程
        Thread consumerThread = new Thread(() -> {
            try {
                for (int i = 0; i <= 5; i++) {
                    // 从队列中取出数据
                    int num = queue.take();
                    System.out.println(Thread.currentThread().getName() + "Consumed: " + num);
                    Thread.sleep(1000);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        // 启动生产者和消费者线程
        producerThread.start();
        consumerThread.start();
        // 等待线程执行完毕
        try {
            producerThread.join();
            consumerThread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

执行输出:

Thread-0Produced: 0
Thread-1Consumed: 0
Thread-0Produced: 1
Thread-1Consumed: 1
Thread-0Produced: 2
Thread-0Produced: 3
Thread-1Consumed: 2
Thread-0Produced: 4
Thread-0Produced: 5
Thread-1Consumed: 3
Thread-1Consumed: 4
Thread-1Consumed: 5

阻塞队列的使用比较简单,这里是个简单的使用例子,可设置合适的队列大小和生产者消费者休眠时间来调试阻塞等待和唤醒通知。使用阻塞队列可解决多线程并发访问数据安全问题,也能方便的实现线程间的协调工作。

总结

通过了解阻塞队列的原理和使用场景,我们可以更好地应对多线程编程中的并发问题,提高代码的可维护性和可扩展性。阻塞队列作为一种常见的并发编程工具,能够帮助我们实现高效的数据传输和线程协作,为我们的应用程序提供更好的性能和可靠性保障。希望本文能够为读者对阻塞队列的理解和应用提供一些帮助。文章来源地址https://www.toymoban.com/news/detail-695554.html

到了这里,关于阻塞队列的原理及应用的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 阻塞队列是什么

    (1) 栈与队列 1)栈:先进后出,后进先出 2)队列:先进先出 (2) 阻塞队列 阻塞:必须要阻塞/不得不阻塞 阻塞队列是一个队列,在数据结构中起的作用如下图: 1)当队列是空的,从队列中获取元素的操作将会被阻塞。 2)当队列是满的,从队列中添加元素的操作将会被阻塞

    2024年02月13日
    浏览(40)
  • 10.阻塞队列和线程池

    阻塞队列(BlockQueue) 非阻塞方法 add 往满的队列中添加元素会报错 remove 从空的队列中移除元素会报错 offer 往满的队列中添加元素会返回false poll 从空的队列中移除元素会返回null 阻塞方法 put take 使用场景: 阻塞队列通常使用在生产者消费者设计模式当中,生产者不用关心生成的

    2024年02月04日
    浏览(35)
  • BlockingQueue阻塞队列

    BlockingQueue简介 juc包下,BlockingQueue很好的解决了多线程中,高效安全的\\\"传输数据\\\"问题。 阻塞队列,是一个队列,可以是数据从队列的一端输入,从另一端输出。 当队列空时,从队列获取元素线程被阻塞,直到其他线程向空的队列插入新元素。 当队列满时,向队列添加元素

    2024年02月05日
    浏览(37)
  • 阻塞队列(BlockingQueue)

    阻塞队列都实现了: BlockingQueue JDK提供的七个阻塞队列 ①. ArrayBlockingQueue 有界 阻塞队列—— 必须指定大小 ——数组 ②. LinkedBlockingQueue 有界 阻塞队列—— 默认大小:Integer.MAX_VALUE最大值 ——链表 ③. LinkedTransferQueue 无界 阻塞队列——链表 ④. PriorityBlockingQueue 无界 阻塞队列

    2024年02月03日
    浏览(36)
  • 07_阻塞队列(BlockingQueue)

    目录 1. 什么是BlockingQueue 2. 认识BlockingQueue 3. 代码演示 栈与队列概念 栈(Stack):先进后出,后进先出 队列:先进先出 在多线程领域:所谓 阻塞 ,在某些情况下会 挂起线程 (即阻塞),一旦条件满足,被挂起的线程又会自动被唤起。 BlockingQueue即阻塞队列 ,是java.util.concur

    2024年02月01日
    浏览(37)
  • 阻塞队列.

    目录 ♫什么是阻塞队列 ♫什么是生产-消费者模式 ♫实现一个阻塞队列 ♫BlockingQueue 阻塞队列是一种特殊的队列,它除了具备队列的先进先出的特点外,还具有以下特点: ♩. 如果队列为空时,执行出队列操作,会阻塞等待,直到另一个线程往队列里添加元素(队列不为空)

    2024年02月08日
    浏览(61)
  • 阻塞队列BlockingQueue

    先进先出的数据结构,允许出队的一端称为队头,允许入队的一端称为队尾。 在java中为队列定义了一个接口规范 Queue 接口 阻塞队列提供了线程安全的队列访问方式。 多线程入队互斥,多线程出队互斥,队列满了阻塞生产者线程,队列空了阻塞消费者线程 阻塞队列的接口

    2023年04月09日
    浏览(34)
  • 阻塞队列(JAVA)

    阻塞队列是一种特殊的队列,也遵守 \\\"先进先出\\\" 的原则。 阻塞队列能是一种 线程安全的数据结构 , 并且具有以下特性: 当队列满的时候, 继续入队列就会阻塞, 直到有其他线程从队列中取走元素; 当队列空的时候, 继续出队列也会阻塞, 直到有其他线程往队列中插入元素。  

    2024年02月02日
    浏览(36)
  • Qt QQueue 安全的多线程队列、阻塞队列

    在C++中,queue是一个模板类,用于实现队列数据结构,遵循先进先出的原则。 ♦ 常用方法: · ♦ 简单使用: · ♦ 打印: · QQueue 继承与 QList ♦ 常用方法: · ♦ 实例: · ♦ 打印: · 在多线程编程中,由于QQueue并不是线程安全的,因此我们需要先使用互斥锁(QMutex)来保

    2024年02月16日
    浏览(39)
  • 【Linux驱动】Linux阻塞IO —— 阻塞读取按键状态(等待队列实现)

    上一节获取按键状态时,是在应用层以循环的方式不断读取按键状态,但是我们实际关注的只是当按键被按下时发生的情况,所以大多数时间拿到的状态都是我们不需要的结果。 对此,当按键被释放时,让 read 接口处于阻塞状态,等按键被按下再解除阻塞。 要使用等待队列

    2024年02月02日
    浏览(43)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包