Java 实现阻塞队列

阻塞队列是线程安全的队列,核心特性:

  1. 入队阻塞:队列满时,生产者线程阻塞,直到队列有空闲位置
  2. 出队阻塞:队列空时,消费者线程阻塞,直到队列有元素
  3. 是生产者-消费者模型的核心组件

我会带你实现最经典的有界阻塞队列 (基于数组+锁+等待/通知机制),这也是 JDK ArrayBlockingQueue 的核心原理。

一、核心实现原理

  1. 数据结构:固定大小的数组存储元素
  2. 线程安全 :使用 ReentrantLock 保证原子操作
  3. 阻塞唤醒Condition 条件队列(比 wait/notify 更精准)
    • notEmpty:队列非空,唤醒消费者
    • notFull:队列非满,唤醒生产者

二、完整代码实现

java 复制代码
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 基于数组实现的有界阻塞队列
 * @param <E> 队列元素类型
 */
public class MyBlockingQueue<E> {
    // 存储元素的数组
    private final Object[] items;
    // 队头指针(取元素用)
    private int takeIndex;
    // 队尾指针(放元素用)
    private int putIndex;
    // 队列元素数量
    private int count;

    // 可重入锁:保证入队/出队的线程安全
    private final ReentrantLock lock;
    // 条件:队列非空(消费者等待/唤醒)
    private final Condition notEmpty;
    // 条件:队列非满(生产者等待/唤醒)
    private final Condition notFull;

    /**
     * 构造方法:指定队列容量
     * @param capacity 队列最大容量
     */
    public MyBlockingQueue(int capacity) {
        if (capacity <= 0) {
            throw new IllegalArgumentException("容量必须大于0");
        }
        this.items = new Object[capacity];
        // 默认非公平锁(也可传true创建公平锁)
        this.lock = new ReentrantLock();
        this.notEmpty = lock.newCondition();
        this.notFull = lock.newCondition();
    }

    /**
     * 阻塞入队:队列满时阻塞线程
     * @param element 入队元素
     * @throws InterruptedException 线程中断异常
     */
    public void put(E element) throws InterruptedException {
        // 加锁:保证原子操作
        lock.lockInterruptibly();
        try {
            // 循环判断:防止虚假唤醒
            while (count == items.length) {
                // 队列满,生产者阻塞,释放锁等待唤醒
                notFull.await();
            }
            // 入队操作
            enqueue(element);
        } finally {
            // 必须释放锁
            lock.unlock();
        }
    }

    /**
     * 阻塞出队:队列空时阻塞线程
     * @return 出队元素
     * @throws InterruptedException 线程中断异常
     */
    public E take() throws InterruptedException {
        lock.lockInterruptibly();
        try {
            // 循环判断:防止虚假唤醒
            while (count == 0) {
                // 队列空,消费者阻塞,释放锁等待唤醒
                notEmpty.await();
            }
            // 出队操作
            return dequeue();
        } finally {
            lock.unlock();
        }
    }

    /**
     * 私有入队方法:仅在加锁后调用
     */
    private void enqueue(E element) {
        items[putIndex] = element;
        // 指针循环(数组环形复用)
        if (++putIndex == items.length) {
            putIndex = 0;
        }
        count++;
        // 唤醒一个等待的消费者
        notEmpty.signal();
    }

    /**
     * 私有出队方法:仅在加锁后调用
     */
    private E dequeue() {
        E element = (E) items[takeIndex];
        items[takeIndex] = null; // 帮助GC回收
        // 指针循环
        if (++takeIndex == items.length) {
            takeIndex = 0;
        }
        count--;
        // 唤醒一个等待的生产者
        notFull.signal();
        return element;
    }

    // 获取队列元素数量
    public int size() {
        lock.lock();
        try {
            return count;
        } finally {
            lock.unlock();
        }
    }
}

三、关键细节解释

1. 为什么用 while 而不是 if

必须用循环判断 ,防止虚假唤醒(线程未被唤醒却自动唤醒),保证队列状态正确。

2. 环形数组设计

takeIndexputIndex 到达数组末尾后重置为0,实现数组空间复用,效率更高。

3. 锁与条件队列

  • 一把 ReentrantLock 控制所有操作,保证线程安全
  • 两个 Condition 精准唤醒:
    • 入队后唤醒消费者
    • 出队后唤醒生产者
      Object.wait/notify 效率更高(不会误唤醒不相关线程)。

四、测试:生产者-消费者模型

java 复制代码
public class BlockingQueueTest {
    public static void main(String[] args) {
        MyBlockingQueue<Integer> queue = new MyBlockingQueue<>(5);

        // 生产者线程:持续入队
        new Thread(() -> {
            try {
                for (int i = 1; i <= 10; i++) {
                    queue.put(i);
                    System.out.println("生产者入队:" + i + ",队列大小:" + queue.size());
                    Thread.sleep(500);
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }, "生产者").start();

        // 消费者线程:持续出队
        new Thread(() -> {
            try {
                while (true) {
                    Integer element = queue.take();
                    System.out.println("消费者出队:" + element + ",队列大小:" + queue.size());
                    Thread.sleep(1000);
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }, "消费者").start();
    }
}

测试效果

  1. 队列满时,生产者自动阻塞
  2. 队列空时,消费者自动阻塞
  3. 线程安全,无数据错乱

五、Java 官方阻塞队列(生产环境用)

实际开发直接用 JUC 包下的实现类,无需手写:

  1. ArrayBlockingQueue:数组实现,有界,公平/非公平可选
  2. LinkedBlockingQueue:链表实现,可选有界/无界
  3. PriorityBlockingQueue:优先级队列,无界
  4. SynchronousQueue:不存储元素,直接传递

总结

  1. 阻塞队列核心:队列满/空时线程阻塞,条件满足时唤醒
  2. 实现关键:ReentrantLock + Condition + 环形数组
  3. 手写核心是理解原理,生产环境直接用 java.util.concurrent 官方实现
  4. 必须用 while 判断队列状态,防止虚假唤醒
相关推荐
阿文的代码库1 小时前
干货分享|C++运算符重载知识点
java·c++·算法
SunnyDays10111 小时前
Java 实现 PDF 转 PDF/A 和 PDF/A 转 PDF(超详细教程)
java·开发语言·pdf
meilindehuzi_a1 小时前
打破0基础:通过 5 个核心案例深度拆解 JavaScript 正则表达式与运行时类型系统
开发语言·javascript·正则表达式
Deep-w1 小时前
【MATLAB】基于 MATLAB 的直流电动机双闭环调速系统建模与仿真
开发语言·算法·matlab
muddjsv1 小时前
Java语言学习路线全解析:从入门到精通的核心模块与进阶路径
java
未若君雅裁1 小时前
线程池核心参数与执行流程
java·开发语言
lbb 小魔仙1 小时前
稳定比技巧更重要:海外多地区数据采集的经验教训
开发语言·javascript·ecmascript
pursue.dreams1 小时前
Windows系统Golang超详细安装配置教程(2026最新、零基础)
开发语言·windows·golang
东方巴黎~Sunsiny1 小时前
后端已经开始使用AI代替前端开发了
java·人工智能·状态模式