【Java EE】阻塞队列(BlockingQueue)

阻塞队列

在Java多线程编程中,生产者-消费者模式 是解耦数据处理最经典的解决方案。而java.util.concurrent包提供的阻塞队列(BlockingQueue),则是实现该模式的核心。

什么是阻塞队列?

阻塞队列是一种支持阻塞式插入和移除的线程安全队列。它具备两个典型特性:

  • 当队列满时:生产者线程尝试插入元素会被阻塞,直到队列有可用空间。
  • 当队列空时:消费者线程尝试移除元素会被阻塞,直到队列中有新元素。

这种特性使得它天然适合处理生产者和消费者速度不匹配的问题。

BlockingQueue常用继承关系

声明关系

java 复制代码
// Queue --- 最顶层的队列接口
public interface Queue<E> extends Collection<E> { }

// AbstractQueue --- Queue 的骨架抽象实现
public abstract class AbstractQueue<E> extends AbstractCollection<E>
    implements Queue<E> { }

// BlockingQueue --- 继承 Queue,增加阻塞操作
public interface BlockingQueue<E> extends Queue<E> { }

// 三个实现类:继承 AbstractQueue 骨架,实现 BlockingQueue 契约
public class ArrayBlockingQueue<E> extends AbstractQueue<E>
    implements BlockingQueue<E>, Serializable { }

public class LinkedBlockingQueue<E> extends AbstractQueue<E>
    implements BlockingQueue<E>, Serializable { }

public class PriorityBlockingQueue<E> extends AbstractQueue<E>
    implements BlockingQueue<E>, Serializable { }

ArrayBlockingQueue ------ 有界数组结构

  • 内部结构 :基于循环数组 + 一把ReentrantLock + 两个Condition(notEmpty、notFull)。
  • 特点:有界且容量固定,支持公平/非公平锁(默认非公平)。
  • 适用:生产消费速度较为均衡、对内存限制严格的场景。
java 复制代码
BlockingQueue<String> queue = new ArrayBlockingQueue<>(100, true); // 公平模式

LinkedBlockingQueue ------ 链表结构

  • 内部结构:基于单向链表 + 两把锁(putLock / takeLock)分离。
  • 特点 :理论上无界(默认Integer.MAX_VALUE),吞吐量高于ArrayBlockingQueue
  • 注意:若不设置容量,可能因生产者过快导致内存溢出。
java 复制代码
BlockingQueue<String> queue = new LinkedBlockingQueue<>(1000);

PriorityBlockingQueue ------ 优先级无界队列

  • 特点 :按元素优先级出队(自然排序或自定义Comparator),无界但可能OOM。
  • 适用:任务调度、医疗分诊等需按优先级处理的场景。
java 复制代码
BlockingQueue<Task> queue = new PriorityBlockingQueue<>(11, 
    (t1, t2) -> t2.getPriority() - t1.getPriority());

📊全量并发队列对比表⭐

队列名称 数据结构 吞吐量 有界性 锁机制 排序特性 典型适用场景
ArrayBlockingQueue 数组 严格有界 单锁(不分离) FIFO 资源受控、内存严格限制、防止OOM的生产消费模型
LinkedBlockingQueue 链表 可选有界(默认Integer.MAX) 双锁(锁分离) FIFO 高吞吐、基于链表的容量可控生产消费(如线程池默认任务队列)
SynchronousQueue 无内部容器 极高 严格为0(无容量) CAS/自旋 任务直传、 Executors.newCachedThreadPool() 的核心实现
DelayQueue 无界 单锁(锁分离) 按延迟时间排序 延迟任务调度、定时重试机制、缓存过期淘汰
PriorityBlockingQueue 无界 单锁(锁分离) 按优先级排序 优先级任务调度、带权重的数据处理、动态Top-N
LinkedTransferQueue 链表 极高 无界 无锁(CAS) FIFO 极高并发下的直接交接、替代SynchronousQueue提升吞吐
LinkedBlockingDeque 双向链表 可选有界 双锁(锁分离) FIFO 工作窃取算法、消费者既可以是生产者也可以是消费者的场景
ConcurrentLinkedQueue 链表 极高 无界 无锁(CAS) FIFO 高并发非阻塞场景、对吞吐量要求极高且不需要阻塞等待的场景
Disruptor (RingBuffer) 环形数组 极致 严格有界 无锁(序号栅栏) 按序发布 金融交易系统、超低延迟事件驱动架构(非JDK内置)
Netty MpscQueue 环形数组 极高 严格有界 无锁(CAS) FIFO Netty EventLoop任务分发、多生产者单消费者极致并发

1. JDK 阻塞队列 - 适合生产者-消费者模式

这类队列实现了 BlockingQueue 接口,当队列满时,put 操作会阻塞;当队列空时,take 操作会阻塞。

  • ArrayBlockingQueue :利用环形数组实现。缺点 是插入和取出使用的是同一把 ReentrantLock,导致生产和消费无法真正并行,所以吞吐量只是"中等"。但它不产生额外对象,内存开销极小。
  • LinkedBlockingQueue :利用单向链表实现。优点 是采用了"锁分离"技术(putLocktakeLock 两把锁),生产者和消费者可以完全并发执行,因此吞吐量"高"。注意 :如果不传初始容量,默认大小为 Integer.MAX_VALUE,极易导致OOM。
  • PriorityBlockingQueue :内部基于 PriorityHeap 实现。允许插入 null 吗?不允许 。元素必须实现 Comparable 或在构造时传入 Comparator。由于每次插入/取出都要堆化,吞吐量中等。
  • DelayQueue :本质上是 PriorityBlockingQueue 的封装,元素必须实现 Delayed 接口。只有元素的延迟时间到了,才能被 take 出来。常用于订单超时取消、心跳检测失败重连等。

2. JDK 非阻塞队列 - 适合高并发轮询模式

这类队列不实现阻塞方法,如果队列空了,取出操作直接返回 null,完全基于 CAS(Compare-And-Swap) 硬件级指令实现,没有锁的开销。

  • ConcurrentLinkedQueue:Java非阻塞队列的标杆。基于链表,使用CAS自旋保证线程安全。在低到中等争用下性能无敌,但在极高争用下,CAS失败的自旋会导致CPU开销飙升。
  • LinkedTransferQueue :这是一个"集大成者"。它结合了 SynchronousQueueLinkedBlockingQueue 的特点。如果有消费者在等待,直接把数据交给消费者(Transfer);如果没有,就放在队列里缓冲。完全无锁实现,极其复杂但也极其高效。

3. 框架级无锁队列 - 适合极致性能场景

当JDK自带的队列无法满足微秒级/纳秒级延迟要求时,通常需要引入第三方框架队列。

  • Disruptor (LMAX) :号称世界上最快的队列。摒弃了链表,使用固定大小的环形数组(预分配内存,解决GC问题)。核心思想是"消除伪共享"(通过缓存行填充 @Contended)和"序列器栅栏"协调生产消费者。通常比 LinkedBlockingQueue 快 10 倍以上。
  • Netty MpscQueueMpsc 全称 Multi-Producer Single-Consumer(多生产者单消费者)。Netty 为了解决 EventLoop 中的外部任务入队问题专门设计的。它也是环形数组,针对"多写一读"的场景做了极致的CAS优化,在Netty内部无处不在。

4. 补充:双端队列

  • LinkedBlockingDeque :相比于普通 Queue 只能在队头出、队尾进,双端队列可以在两端 都进行插入和移除。这非常适合工作窃取模式。比如 ForkJoinPool 中,每个线程都有自己的双端队列,自己从头部拿任务执行,空闲的线程去"偷"其他线程队列尾部的任务,极大地提高了线程利用率。

⚙️ 选型总结

  1. 最稳妥的默认选择LinkedBlockingQueue(记得指定容量,防OOM)。
  2. 内存极其敏感ArrayBlockingQueue
  3. 不要缓冲池,必须一手交钱一手交货SynchronousQueue
  4. 需要按时间/优先级处理DelayQueue / PriorityBlockingQueue
  5. 不需要阻塞等待,追求高并发读ConcurrentLinkedQueue
  6. 消费者也是生产者(工作窃取)LinkedBlockingDeque
  7. 面临千万级TPS、延迟要求微秒级 :放弃JDK队列,直接上 DisruptorNetty MpscQueue

BlockingQueue核心方法

BlockingQueue提供了四种添加/移除元素的方法:异常、布尔值、阻塞、超时策略

抛出异常 返回特殊值 阻塞 超时退出
插入 add(e) offer(e) put(e) offer(e, time, unit)
移除 remove() poll() take() poll(time, unit)
查看队列头部元素 element() peek()
  • 抛出异常 :操作失败直接抛IllegalStateExceptionNoSuchElementException
  • 返回特殊值 :插入返回true/false,移除返回元素或null
  • 阻塞:操作未满足条件时线程阻塞。
  • 超时退出 :阻塞一段时间后仍不满足则返回falsenull

调用put()take()时,务必妥善处理InterruptedException,不能只打印日志就吞掉异常,否则可能导致线程无法正确关闭。

java 复制代码
try {
    queue.take();
} catch (InterruptedException e) {
    Thread.currentThread().interrupt();  // 重置中断标志
    return;
}
java 复制代码
//take()示例
public static void main(String[] args) throws InterruptedException {
    //创建了一个线程安全的LinkedBlockingQueue,用于存储字符串元素。
    BlockingQueue<String> queue = new LinkedBlockingQueue<>();
    // 使用take()方法从队列中获取并移除头元素。
    // 如果队列为空,这个方法会阻塞(等待),直到有元素可用或线程被中断。
    String elem = queue.take();
    System.out.println(elem);
}

没有任何输出结果,用jconsole可以看到main线程处于WAITING状态

java 复制代码
//put(e)示例
public static void main(String[] args)throws InterruptedException  {
    BlockingQueue<String> queue = new LinkedBlockingQueue<>(100);//最大容量100
    for (int i=0;i<=100;i++){//加入101个元素
        queue.put(String.valueOf(i));
    }
    System.out.println("插入元素结束");
}

没有任何输出结果,用jconsole可以看到main线程处于WAITING状态

手搓阻塞队列(泛型实现)

MyBlockingQueue的成员变量与构造函数

java 复制代码
public class MyBlockingQueue<T> {
    // 使用泛型数组,初始化时需要强转
    private T[] data = null;

    // 队首索引
    private int head = 0;

    // 队尾索引
    private int tail = 0;

    // 当前元素个数
    private int size = 0;

    @SuppressWarnings("unchecked")
    public MyBlockingQueue(int capacity) {
        // 在 Java 中不能直接 new T[capacity],通常 new Object 数组再强转
        data = (T[]) new Object[capacity];
    }
}

MyBlockingQueue入队:put(T elem)

java 复制代码
public void put(T elem) throws InterruptedException {
    synchronized (this) {
        // 使用 while 循环检查,防止虚假唤醒 (Spurious Wakeup)
        while (size >= data.length) {
            // 队列满了,进入等待状态,释放锁
            this.wait();
        }
        
        //入队
	    data[tail] = elem;
	    tail = (tail + 1) % data.length;
        size++;
        
        // 唤醒可能正在等待 take 的线程
        this.notifyAll();
    }
}
机制 说明
synchronized 保证对缓冲区的操作线程安全, sizeheadtail 的修改不会乱套(原子性)
wait() 条件不满足时(满/空)让出锁并阻塞
notifyAll() 唤醒所有等待线程,重新检查条件
while 循环 防止虚假唤醒,每次醒来都要重新判断条件

MyBlockingQueue出队:take()

java 复制代码
public T take() throws InterruptedException {
       synchronized (this) {
           // 队列为空,进入等待状态
           while (size == 0) {
               this.wait();
           }
		   //出队
           T ret = data[head];
           // 帮助 GC:取出元素后将原位置置为空(可选,但对于泛型对象是个好习惯)
           data[head] = null;
		   head = (head + 1) % data.length;
           size--;
           
           // 唤醒可能正在等待 put 的线程
           this.notifyAll();
           return ret;
       }
   }
机制 说明
synchronized 保证对缓冲区的操作线程安全, sizeheadtail 的修改不会乱套(原子性)
wait() 条件不满足时(满/空)让出锁并阻塞
notifyAll() 唤醒所有等待线程,重新检查条件
while 循环 防止虚假唤醒,每次醒来都要重新判断条件

完整测试(生产者消费者模型)

java 复制代码
/**
 * 泛型阻塞队列实现
 * @param <T> 队列中元素的类型
 */
public class MyBlockingQueue<T> {
    // 使用泛型数组,初始化时需要强转
    private T[] data = null;

    // 队首索引
    private int head = 0;

    // 队尾索引
    private int tail = 0;

    // 当前元素个数
    private int size = 0;

    @SuppressWarnings("unchecked")
    public MyBlockingQueue(int capacity) {
        // 在 Java 中不能直接 new T[capacity],通常 new Object 数组再强转
        data = (T[]) new Object[capacity];
    }

    /**
     * 入队列
     */
    public void put(T elem) throws InterruptedException {
        synchronized (this) {
            // 使用 while 循环检查,防止虚假唤醒 (Spurious Wakeup)
            while (size >= data.length) {
                // 队列满了,进入等待状态,释放锁
                this.wait();
            }

            data[tail] = elem;
            tail++;
            // 环形结构处理:如果到达数组末尾,回到开头
            if (tail >= data.length) {
                tail = 0;
            }

            size++;
            // 唤醒可能正在等待 take 的线程
            this.notifyAll();
        }
    }

    /**
     * 出队列
     */
    public T take() throws InterruptedException {
        synchronized (this) {
            // 队列为空,进入等待状态
            while (size == 0) {
                this.wait();
            }

            T ret = data[head];
            // 帮助 GC:取出元素后将原位置置为空(可选,但对于泛型对象是个好习惯)
            data[head] = null;

            head++;
            if (head >= data.length) {
                head = 0;
            }

            size--;
            // 唤醒可能正在等待 put 的线程
            this.notifyAll();
            return ret;
        }
    }
    public static void main(String[] args) {
        // 创建一个容量为 3 的阻塞队列
        MyBlockingQueue<Integer> queue = new MyBlockingQueue<>(3);

        // 消费者线程:每隔 1 秒取出一个元素
        Thread customer = new Thread(() -> {
            try {
                while (true) {
                    int value = queue.take();
                    System.out.println("消费了元素: " + value);
                    Thread.sleep(1000); // 故意放慢消费速度,测试队列满的情况
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "消费者");

        // 生产者线程:快速生产元素
        Thread producer = new Thread(() -> {
            try {
                int count = 1;
                while (true) {
                    System.out.println("准备生产元素: " + count);
                    queue.put(count);
                    System.out.println("生产成功: " + count);
                    count++;
                    // 生产速度很快,不休眠
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "生产者");

        customer.start();
        producer.start();
    }

}

生产者消费者模型

模型说明

  • 生产者:不断生产数据,放入共享缓冲区
  • 消费者:不断消费数据,从共享缓冲区取出
  • 缓冲区:有大小限制,满则生产者等待,空则消费者等待

代码实战

java 复制代码
/**
 * 生产者-消费者模式演示
 * 使用 BlockingQueue (阻塞队列) 实现多线程间的数据传递与解耦
 */
public class Demo {
    // 定义一个"毒丸"元素,用于通知消费者安全退出
    // 注意:LinkedBlockingQueue 不允许放入 null 值,所以用一个特殊对象作为结束标志
    private static final Integer POISON_PILL = -1;

    public static void main(String[] args) throws InterruptedException {
        // 1. 创建一个容量为 5 的有界阻塞队列
        BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(5);

        // 2. 定义生产者任务
        // 扩充为多生产者,每个生产者内部维护自己的计数器,避免多线程共享变量导致的并发问题
        Runnable producerTask = () -> {
            String threadName = Thread.currentThread().getName();
            int n = 0;
            try {
                // 每个生产者只生产 10 个元素
                while (n < 10) {
                    // put() 方法:如果队列已满,当前线程会阻塞(挂起),直到队列有空位才放入
                    queue.put(n);
                    System.out.println("【" + threadName + "】 生产元素 >>> " + n);
                    n++;

                    // 稍微休眠,模拟生产耗时,让效果更明显
                    Thread.sleep((long) (Math.random() * 200));
                }
                System.out.println("【" + threadName + "】 生产完毕,退出。");
            } catch (InterruptedException e) {
                // 如果线程在阻塞时被中断,恢复中断标志并退出
                Thread.currentThread().interrupt();
            }
        };

        // 3. 定义消费者任务
        Runnable consumerTask = () -> {
            String threadName = Thread.currentThread().getName();
            try {
                while (true) {
                    // take() 方法:如果队列已空,当前线程会阻塞(挂起),直到队列有新元素才取出
                    Integer element = queue.take();

                    // 判断是否取到了"毒丸",如果是,则将毒丸放回队列(供其他消费者退出),然后自己退出
                    if (element.equals(POISON_PILL)) {
                        System.out.println("【" + threadName + "】 收到毒丸,准备安全退出...");
                        queue.put(POISON_PILL); // 把毒丸放回去,让其他消费者也能看到
                        break;
                    }

                    System.out.println("    <<<【" + threadName + "】 消费元素 " + element + " | 当前队列长度: " + queue.size());

                    // 模拟处理业务的耗时(消费速度慢于生产速度)
                    Thread.sleep(500);
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        };

        // 4. 创建并启动 3 个生产者线程
        Thread p1 = new Thread(producerTask, "生产者-1");
        Thread p2 = new Thread(producerTask, "生产者-2");
        Thread p3 = new Thread(producerTask, "生产者-3");

        // 5. 创建并启动 2 个消费者线程
        Thread c1 = new Thread(consumerTask, "消费者-A");
        Thread c2 = new Thread(consumerTask, "消费者-B");

        System.out.println("====== 开始生产消费流程 ======");
        c1.start();
        c2.start();
        p1.start();
        p2.start();
        p3.start();

        // 6. 优雅停机逻辑 (在主线程中执行)
        // 等待所有生产者线程执行完毕
        p1.join();
        p2.join();
        p3.join();
        System.out.println("====== 所有生产者均已停止,准备投入毒丸 ======");

        // 因为有 2 个消费者,所以需要向队列中放入 2 个"毒丸"
        queue.put(POISON_PILL);
        queue.put(POISON_PILL);

        // 等待所有消费者线程执行完毕
        c1.join();
        c2.join();

        System.out.println("====== 所有消费者均已停止,程序结束 ======");
    }
}
text 复制代码
====== 开始生产消费流程 ======
【生产者-2】 生产元素 >>> 0
【生产者-3】 生产元素 >>> 0
【生产者-1】 生产元素 >>> 0
    <<<【消费者-B】 消费元素 0 | 当前队列长度: 1
    <<<【消费者-A】 消费元素 0 | 当前队列长度: 1
【生产者-1】 生产元素 >>> 1
【生产者-1】 生产元素 >>> 2
【生产者-3】 生产元素 >>> 1
【生产者-2】 生产元素 >>> 1
    <<<【消费者-B】 消费元素 1 | 当前队列长度: 3
    <<<【消费者-A】 消费元素 0 | 当前队列长度: 3
【生产者-2】 生产元素 >>> 2
【生产者-1】 生产元素 >>> 3
【生产者-3】 生产元素 >>> 2
【生产者-2】 生产元素 >>> 3
    <<<【消费者-B】 消费元素 2 | 当前队列长度: 4
    <<<【消费者-A】 消费元素 1 | 当前队列长度: 4
【生产者-1】 生产元素 >>> 4
    <<<【消费者-A】 消费元素 1 | 当前队列长度: 4
    <<<【消费者-B】 消费元素 3 | 当前队列长度: 5
【生产者-2】 生产元素 >>> 4
    <<<【消费者-A】 消费元素 2 | 当前队列长度: 3
【生产者-3】 生产元素 >>> 3
    <<<【消费者-B】 消费元素 2 | 当前队列长度: 3
【生产者-2】 生产元素 >>> 5
    <<<【消费者-B】 消费元素 3 | 当前队列长度: 4
【生产者-1】 生产元素 >>> 5
    <<<【消费者-A】 消费元素 4 | 当前队列长度: 4
【生产者-2】 生产元素 >>> 6
    <<<【消费者-A】 消费元素 4 | 当前队列长度: 4
【生产者-3】 生产元素 >>> 4
    <<<【消费者-B】 消费元素 3 | 当前队列长度: 4
【生产者-2】 生产元素 >>> 7
    <<<【消费者-B】 消费元素 5 | 当前队列长度: 4
【生产者-1】 生产元素 >>> 6
【生产者-3】 生产元素 >>> 5
    <<<【消费者-A】 消费元素 5 | 当前队列长度: 5
    <<<【消费者-B】 消费元素 6 | 当前队列长度: 4
【生产者-2】 生产元素 >>> 8
    <<<【消费者-A】 消费元素 4 | 当前队列长度: 4
【生产者-1】 生产元素 >>> 7
    <<<【消费者-A】 消费元素 7 | 当前队列长度: 4
【生产者-3】 生产元素 >>> 6
    <<<【消费者-B】 消费元素 6 | 当前队列长度: 4
【生产者-1】 生产元素 >>> 8
    <<<【消费者-A】 消费元素 5 | 当前队列长度: 4
【生产者-2】 生产元素 >>> 9
    <<<【消费者-B】 消费元素 8 | 当前队列长度: 4
【生产者-3】 生产元素 >>> 7
【生产者-2】 生产完毕,退出。
    <<<【消费者-B】 消费元素 7 | 当前队列长度: 4
【生产者-1】 生产元素 >>> 9
    <<<【消费者-A】 消费元素 6 | 当前队列长度: 4
【生产者-3】 生产元素 >>> 8
【生产者-1】 生产完毕,退出。
    <<<【消费者-B】 消费元素 8 | 当前队列长度: 4
【生产者-3】 生产元素 >>> 9
    <<<【消费者-A】 消费元素 9 | 当前队列长度: 4
【生产者-3】 生产完毕,退出。
====== 所有生产者均已停止,准备投入毒丸 ======
    <<<【消费者-A】 消费元素 7 | 当前队列长度: 4
    <<<【消费者-B】 消费元素 9 | 当前队列长度: 4
    <<<【消费者-A】 消费元素 8 | 当前队列长度: 3
    <<<【消费者-B】 消费元素 9 | 当前队列长度: 2
【消费者-A】 收到毒丸,准备安全退出...
【消费者-B】 收到毒丸,准备安全退出...
====== 所有消费者均已停止,程序结束 ======

生产者消费者模型的优势

1. 解耦 (Decoupling)

  • 逻辑分离:生产者不需要知道消费者的存在,反之亦然。生产者只管往队列里塞数据,消费者只管从队列里拿数据。
  • 代码维护:如果你想修改消费者的处理逻辑(比如从存入数据库改为发送到消息队列),完全不需要改动生产者的代码。

2. 支持并发,提升效率 (Concurrency & Performance)

  • 异步处理:生产者生产完数据后可以立刻去生产下一个,不需要等待消费者处理完。
  • 平衡差异:如果生产数据很快(比如读取传感器数据),而处理数据很慢(比如复杂的图像识别),该模型可以让多个消费者同时工作,利用多核 CPU 提高整体吞吐量。

3. 削峰填谷 (Smoothing Traffic Spikes)

  • 缓冲作用:当遇到突发流量(比如秒杀活动)时,大量请求(生产者)涌入。如果没有缓冲区,系统可能会直接崩溃。
  • 流量整形:阻塞队列像一个水库,把瞬间的高压挡住,让消费者按照自己稳定的节奏(水位)去慢慢处理,保证系统的稳定性。

生产者消费者模型的劣势

1. 增加系统复杂度

  • 并发控制 :正如你写的 MyBlockingQueue 所示,你需要处理锁(synchronized)、线程通信(wait/notify)以及虚假唤醒等底层细节。
  • 容错处理:如果队列满了生产者该怎么办?如果队列空了消费者等多久?这些都需要额外的逻辑去处理。

2. 潜在的性能开销

  • 锁竞争 (Lock Contention) :如果大量线程频繁操作同一个队列,synchronized 带来的锁竞争会成为性能瓶颈。
  • 上下文切换 :线程的阻塞(wait)和唤醒(notify)涉及到操作系统的内核态切换,频繁的切换会消耗不少 CPU 资源。

3. 存在延迟 (Latency)

  • 数据不是直接从生产者传给消费者的,而是经过了内存中的队列中转。对于一些对时间极其敏感(微秒级延迟)的系统(如高频交易),这种中转带来的延迟可能是不可接受的。

4. 资源风险

  • 内存溢出 (OOM):如果生产速度长期大于消费速度,且队列没有设置上限(无界队列),最终会导致内存耗尽。
  • 数据丢失:如果消费者处理失败或系统崩溃,留在队列里的数据可能会丢失,需要额外的持久化机制来保障。
相关推荐
AndreasEmil2 小时前
基于多设计模式的抽奖系统 - 测试报告
java·selenium·设计模式·postman
星轨zb2 小时前
什么是Spring设计模式:单例、工厂与代理
java·spring·设计模式
Seven972 小时前
Tomcat Server的设计和实现:StandardServer
java
s6516654962 小时前
Makefile语法学习
java·linux·前端
Rabitebla2 小时前
二分查找(含有动画展示):不再写出死循环
java·开发语言
人道领域2 小时前
【LeetCode刷题日记】150.逆波兰表达式求值
java·数据结构·算法·leetcode
蓝桉~MLGT2 小时前
中级软考(软件工程师)算法特辑——常考的六大基础排序算法
java·算法·排序算法
悟空爬虫-彪哥2 小时前
Stich接入Codex教程
java·前端·数据库
极客先躯2 小时前
高级java每日一道面试题-2025年11月14日-行业专题[LangChain4j]-如何用 LLM 辅助信用风险评估?
java·数据库·langchain·可解释性·数据来源·分析方式·风险识别