ArrayBlockingQueue &  LinkedBlockingQueue & LinkedBlockingDeque的内部实现

本文详细对比了ArrayBlockingQueue、ArrayList、LinkedBlockingQueue、LinkedBlockingDeque四种数据结构的内部实现,重点分析了它们的存储结构、容量设定及生产消费机制。通过对ArrayBlockingQueue和LinkedBlockingDeque的实例代码解析,展示了如何利用ReentrantLock和Condition实现高并发下的队列操作。

区别

  1. ,ArrayBlockingQueue  与 ArrayList 的内部存储结构为数组;LinkedBlockingQueue 存储是一个单向链表; LinkedBlockingDeque 与 LinkedList 是一个存储双向链表 (后三者都有指引head与tail的Node)
  2. ArrayBlockingQueue的初始化时是一定要外部指定容量大小的;而LinkedBlockingQueue & LinkedBlockingDeque提供默认容量Integer.MAX_VALUE。
  3. 对于消费和生产: ArrayBlockingQueue & LinkedBlockingDeque 通过同一把锁的不同Condition来控制;而LinkedBlockingQueue则通过锁分离的方式来控制。(其生产与消费所竞争的资源是属于2把锁,生产线程要唤醒生产线程,消费线程也要唤醒生产线程; 消费线程唤醒消费线程,消费线程也要唤醒生产线程

综述    

两者都是先加锁ReentrantLock, 依赖于这个锁对于 [出队]等待队列  (Condition -> notEmpty)与 【入队】等待队列(Condition -> notFull) 来控制高并发下的出入。(锁默认用的是非公平模式:NonfairSync)

可以这么理解:

  • notEmpty: 当前队列不是空的,可以消费; 所以出队时,判定队列为空,则await这个信号;队列不为空则进行出队操作,成功后唤醒 notFull ;
  • notFull : 当前队列不是满的, 可以生产;   所以入队时,判定队列已满,则await这个信号。队列没满则进行入队操作,成功后唤醒notEmpty;

785e4fc5eb28ccec1f01db8886464b55d80.jpg

出队

拿ArrayBlockingQueue 的出队来分析:
9c270642edc39dd74e2883fc692e8d100d3.jpg

  1. 先加锁。
  2. 判定当前的数量是否为空
    1. 如果为空,则notEmpty(“队列不是空的”信号) 等待。
    2. 如果不为空,则出队。 出队成功后, 唤醒 notFull ( "队列不是满的" 信号)。
      6ddb37045bbcde4e84cf7eb3f48e91c3a71.jpg
  3. 最终释放锁。

入队

拿LinkedBlockingDeque 的入队来分析:
dd77ab8f6726b66b2aacc78faac7cc9f024.jpg

  1. 先加锁。
  2. 尝试出队操作:
    1. 在unlinkFirst方法中, 从首节点进行出队,出队完毕后,设置好新的First-Node,  唤醒 notFull ( "队列不是满的" 信号)。
      0b962975b5c7c814bd0146a27ec08ca119f.jpg
    2. 如果出队返回是null,则 notEmpty(“队列不是空的”信号) 等待。

例子

模拟ArrayBlockingQueue的生产和消费方式

package com.noob.learn.netty;

import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class Storage {

    private int             capacity = 10;
    private Queue<Object>   queue    = new LinkedList<Object>();
    final ReentrantLock     lock     = new ReentrantLock();

    /** 当前队列不是空的,可以消费 */
    private final Condition notEmpty = lock.newCondition();

    /** 当前队列不是满的, 可以生产 */
    private final Condition notFull  = lock.newCondition();

    public void produce() {
        final ReentrantLock lock = this.lock;
        try {
            lock.lockInterruptibly();

            while (queue.size() > capacity - 1) {
                System.out.println("库存量:" + queue.size() + "已满, 暂时不能执行生产任务!");
                notFull.await();
            }

            queue.add(new Object());
            System.out.println("生产了, 现仓储量为:" + queue.size());
            notEmpty.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();

        }
    }

    public void consume() {
        final ReentrantLock lock = this.lock;
        try {
            lock.lockInterruptibly();
            while (queue.size() == 0) {
                System.out.println("库存量" + queue.size() + "暂时不能执行消费任务!");
                notEmpty.wait();
            }
            queue.remove();
            System.out.println("消费了, 现仓储量为:" + queue.size());
            notFull.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();

        }
    }

    public static void main(String[] args) throws InterruptedException {
        Storage storage = new Storage();
        new Thread(() -> {
            while (true) {
                storage.produce();
                try {
                    Thread.sleep(1000); // 执行太快了降速
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    }   ).start();

        new Thread(() -> {
            while (true) {
                storage.consume();
                try {
                    Thread.sleep(1000);// 执行太快了降速
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }   ).start();
    }
}

执行结果:7be3ce93cdf864260b13a9d084813ce2f2e.jpg

 


 

 

 

 

 

 

转载于:https://my.oschina.net/u/3434392/blog/3013662

### ArrayBlockingQueue LinkedBlockingQueue 对比及实际应用场景 ArrayBlockingQueue LinkedBlockingQueueJava 中两种常用的阻塞队列实现,它们在底层数据结构、锁机制、性能表现适用场景上存在显著差异。 #### 数据结构容量控制 ArrayBlockingQueue 基于数组实现,默认情况下是有界队列[^2]。这意味着其容量是固定的,在初始化时必须指定大小,并且不能动态扩展。这种特性使得它非常适合用于需要严格控制资源上限的场景,例如线程池中的任务队列或有限缓冲区的设计中。由于容量固定,开发者可以更精确地管理内存使用情况,从而避免潜在的内存溢出问题。 相对而言,LinkedBlockingQueue 使用链表结构来存储元素,既可以配置为有界也可以是无界的(默认容量为 Integer.MAX_VALUE)[^3]。这种灵活性让它更适合那些可能面临大量并发操作但又不希望频繁调整队列大小的应用环境。 #### 锁机制访问效率 ArrayBlockingQueue 在读写操作时采用的是单锁机制,即对整个队列的操作都由一把锁保护[^2]。这种方式简化了同步逻辑,但在高并发环境下可能会成为瓶颈,因为所有尝试修改队列的操作都需要竞争同一把锁。 相反,LinkedBlockingQueue 利用了双锁机制:插入删除操作分别被不同的锁所控制。这允许生产者消费者能够并行地进行入队出队操作,减少了线程间的相互等待时间,提高了整体吞吐量[^5]。 #### 性能考量选择建议 对于小规模的数据集以及需要保证 FIFO 顺序性的应用来说,ArrayBlockingQueue 通常会表现出较好的性能[^2]。尤其是在队列容量较小的情况下,其基于数组的连续内存布局有助于减少缓存未命中率,提升访问速度。此外,当应用程序要求公平性时,可以通过构造函数参数开启公平模式,尽管这样做会牺牲一定的性能以换取更可预测的行为[^1]。 另一方面,如果系统预期会有大量的并发请求并且数据流波动较大,则应优先考虑使用 LinkedBlockingQueue。这类场景包括日志收集系统、大型分布式任务调度平台等,其中保持较高的并发能力良好的伸缩性至关重要。 #### 实际应用案例 一个典型的适合采用 ArrayBlockingQueue 的例子是在线游戏服务器中的玩家匹配服务。在这个情境下,每个房间最多容纳固定数量的玩家,而一旦达到上限就需要创建新的房间实例。通过设置适当大小的 ArrayBlockingQueue 来暂存等待加入现有房间的玩家信息,可以有效地平衡负载同时防止过度消耗资源。 ```java // 示例代码 - 创建基本使用 import java.util.concurrent.ArrayBlockingQueue; public class FairnessQueueExample { public static void main(String[] args) throws InterruptedException { // 使用公平模式的ArrayBlockingQueue ArrayBlockingQueue&lt;Integer&gt; blockingQueue = new ArrayBlockingQueue&lt;&gt;(10, true); blockingQueue.put(1); System.out.println(blockingQueue.take()); } } ``` 另一个例子可能是嵌入式设备上的传感器数据处理模块。这些设备往往具有严格的硬件限制,因此需要尽可能精确地规划可用内存空间。此时利用 ArrayBlockingQueue 固定大小的特点,可以帮助开发人员更好地理解优化系统的运行状态。 综上所述,在决定是否选用 ArrayBlockingQueue 之前,应当仔细评估项目的具体需求。如果项目强调确定性可控性,那么即使牺牲部分性能也值得选择 ArrayBlockingQueue;反之,若追求极致的并发表现灵活的容量管理,则应该倾向于使用 LinkedBlockingQueue
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值