🔒 阻塞队列(BlockingQueue):生产者消费者的桥梁!

25 阅读11分钟

"等待不是浪费时间,而是为了更好的协作!" 🤝


📖 一、什么是阻塞队列?从包子铺说起

1.1 生活中的场景

想象一个包子铺:

没有阻塞队列的世界:

👨‍🍳 老板疯狂做包子(生产者)
👤 顾客疯狂买包子(消费者)

问题:
- 包子做太快,放不下了!老板手忙脚乱 😰
- 包子卖完了,顾客干等着!老板累死累活 😩
- 两边都不爽!

有了阻塞队列:

👨‍🍳 老板:做包子 → 放进蒸笼(队列)
           如果蒸笼满了,就等等(阻塞)☕
           
👤 顾客:从蒸笼取包子(队列)
       如果蒸笼空了,就等等(阻塞)⏰
       
蒸笼(阻塞队列):
- 自动控制容量
- 自动协调生产和消费
- 完美平衡!✨

1.2 专业定义

阻塞队列(BlockingQueue) 是 Java 并发包(java.util.concurrent)提供的线程安全队列,具有以下特点:

核心特性:

  • 线程安全:多线程环境下自动同步
  • 自动阻塞:队列满时插入阻塞,队列空时获取阻塞
  • 生产者-消费者模式:天然支持,无需手动加锁
  • ⚡ 解决了多线程协作的复杂同步问题

🎯 二、阻塞队列的核心操作

2.1 四种操作方式

BlockingQueue 提供了4种不同的操作方式来处理"队列满"和"队列空"的情况:

操作类型抛异常返回特殊值阻塞等待超时退出
插入add(e)offer(e)put(e)offer(e, time, unit)
删除remove()poll()take()poll(time, unit)
检查element()peek()--

2.2 详细说明

🔴 抛异常(Throws Exception)

add(e)      - 插入成功返回true,队列满抛IllegalStateException
remove()    - 删除成功返回元素,队列空抛NoSuchElementException
element()   - 返回队头元素,队列空抛NoSuchElementException

🟡 返回特殊值(Special Value)

offer(e)    - 插入成功返回true,失败返回false
poll()      - 删除成功返回元素,队列空返回null
peek()      - 返回队头元素,队列空返回null

🟢 阻塞等待(Blocks)⭐ 最常用

put(e)      - 插入元素,队列满时一直等待(阻塞)
take()      - 删除元素,队列空时一直等待(阻塞)

🔵 超时退出(Times Out)

offer(e, timeout, unit)  - 插入元素,等待指定时间后超时
poll(timeout, unit)      - 删除元素,等待指定时间后超时

2.3 操作对比图

队列状态:[满] [正常] [空]

add(e):     ❌抛异常   ✅成功    ✅成功
offer(e):   ❌返回false ✅成功   ✅成功
put(e):     ⏰等待     ✅成功    ✅成功  ← 阻塞!

remove():   ✅成功     ✅成功    ❌抛异常
poll():     ✅成功     ✅成功    ❌返回null
take():     ✅成功     ✅成功    ⏰等待   ← 阻塞!

🏗️ 三、BlockingQueue的实现类

Java 提供了多种阻塞队列实现,每种有不同的特点:

3.1 ArrayBlockingQueue(有界数组队列)

特点

  • 🔹 底层:数组
  • 🔹 容量:有界(必须指定容量)
  • 🔹 锁:单锁(读写共用一把锁)
  • 🔹 公平性:可选(FIFO)

代码示例

import java.util.concurrent.*;

public class ArrayBlockingQueueDemo {
    public static void main(String[] args) throws InterruptedException {
        // 创建容量为3的阻塞队列
        BlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
        
        // 生产者
        new Thread(() -> {
            try {
                for (int i = 1; i <= 5; i++) {
                    System.out.println("生产:包子" + i);
                    queue.put("包子" + i);  // 队列满时阻塞
                    Thread.sleep(1000);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "生产者").start();
        
        // 消费者
        new Thread(() -> {
            try {
                Thread.sleep(2000);  // 延迟启动
                while (true) {
                    String item = queue.take();  // 队列空时阻塞
                    System.out.println("消费:" + item);
                    Thread.sleep(1500);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "消费者").start();
    }
}

输出示例:

生产:包子1
生产:包子2
生产:包子3
生产:包子4  ← 此时队列满,生产者阻塞
消费:包子1  ← 消费者取走一个,生产者继续
生产:包子5
消费:包子2
...

3.2 LinkedBlockingQueue(有界/无界链表队列)

特点

  • 🔹 底层:链表
  • 🔹 容量:有界或无界(默认Integer.MAX_VALUE)
  • 🔹 锁:双锁(读锁putLock + 写锁takeLock)
  • 🔹 性能:读写分离,并发性能更好

代码示例

// 无界队列(容量为Integer.MAX_VALUE)
BlockingQueue<Integer> queue1 = new LinkedBlockingQueue<>();

// 有界队列(容量为100)
BlockingQueue<Integer> queue2 = new LinkedBlockingQueue<>(100);

// 示例:生产者-消费者
public class LinkedBlockingQueueDemo {
    public static void main(String[] args) {
        BlockingQueue<String> queue = new LinkedBlockingQueue<>(5);
        
        // 3个生产者
        for (int i = 1; i <= 3; i++) {
            final int id = i;
            new Thread(() -> {
                try {
                    for (int j = 1; j <= 10; j++) {
                        String product = "产品-" + id + "-" + j;
                        queue.put(product);
                        System.out.println("生产者" + id + " 生产:" + product);
                        Thread.sleep(100);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }, "生产者" + i).start();
        }
        
        // 2个消费者
        for (int i = 1; i <= 2; i++) {
            final int id = i;
            new Thread(() -> {
                try {
                    while (true) {
                        String product = queue.take();
                        System.out.println("  消费者" + id + " 消费:" + product);
                        Thread.sleep(200);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }, "消费者" + i).start();
        }
    }
}

3.3 PriorityBlockingQueue(无界优先级队列)

特点

  • 🔹 底层:(数组实现)
  • 🔹 容量:无界(自动扩容)
  • 🔹 顺序:优先级顺序(不是FIFO)
  • 🔹 线程安全:是

代码示例

class Task implements Comparable<Task> {
    String name;
    int priority;  // 数字越大优先级越高
    
    public Task(String name, int priority) {
        this.name = name;
        this.priority = priority;
    }
    
    @Override
    public int compareTo(Task other) {
        return other.priority - this.priority;  // 降序
    }
    
    @Override
    public String toString() {
        return name + "(P" + priority + ")";
    }
}

public class PriorityBlockingQueueDemo {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue<Task> queue = new PriorityBlockingQueue<>();
        
        // 生产者:乱序添加任务
        queue.put(new Task("任务A", 3));
        queue.put(new Task("任务B", 10));
        queue.put(new Task("任务C", 5));
        queue.put(new Task("任务D", 1));
        
        // 消费者:按优先级取出
        System.out.println("按优先级执行:");
        while (!queue.isEmpty()) {
            System.out.println(queue.take());
        }
    }
}

输出:

按优先级执行:
任务B(P10)  ← 最高优先级
任务C(P5)
任务A(P3)
任务D(P1)

3.4 DelayQueue(延迟队列)

特点

  • 🔹 底层:PriorityQueue
  • 🔹 容量:无界
  • 🔹 特性:元素到期后才能取出
  • 🔹 应用:定时任务、缓存过期

代码示例

class DelayedTask implements Delayed {
    String name;
    long executeTime;  // 执行时间(毫秒)
    
    public DelayedTask(String name, long delaySeconds) {
        this.name = name;
        this.executeTime = System.currentTimeMillis() + delaySeconds * 1000;
    }
    
    @Override
    public long getDelay(TimeUnit unit) {
        long diff = executeTime - System.currentTimeMillis();
        return unit.convert(diff, TimeUnit.MILLISECONDS);
    }
    
    @Override
    public int compareTo(Delayed o) {
        return Long.compare(this.executeTime, ((DelayedTask) o).executeTime);
    }
    
    @Override
    public String toString() {
        return name;
    }
}

public class DelayQueueDemo {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue<DelayedTask> queue = new DelayQueue<>();
        
        // 添加延迟任务
        queue.put(new DelayedTask("任务1-延迟5秒", 5));
        queue.put(new DelayedTask("任务2-延迟2秒", 2));
        queue.put(new DelayedTask("任务3-延迟8秒", 8));
        
        System.out.println("开始时间:" + System.currentTimeMillis());
        
        // 取出任务(会阻塞到任务到期)
        while (!queue.isEmpty()) {
            DelayedTask task = queue.take();
            System.out.println(System.currentTimeMillis() + " - 执行:" + task);
        }
    }
}

输出:

开始时间:1699000000000
1699000002000 - 执行:任务2-延迟2秒  ← 2秒后
1699000005000 - 执行:任务1-延迟5秒  ← 5秒后
1699000008000 - 执行:任务3-延迟8秒  ← 8秒后

3.5 SynchronousQueue(同步队列)

特点

  • 🔹 容量:0(不存储元素)
  • 🔹 特性:生产者必须等待消费者,一对一交接
  • 🔹 应用:线程池(Executors.newCachedThreadPool)

代码示例

public class SynchronousQueueDemo {
    public static void main(String[] args) {
        BlockingQueue<String> queue = new SynchronousQueue<>();
        
        // 生产者
        new Thread(() -> {
            try {
                System.out.println("生产者准备放入数据...");
                queue.put("数据1");  // 阻塞,直到有消费者取走
                System.out.println("生产者成功放入数据!");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "生产者").start();
        
        // 消费者(延迟3秒)
        new Thread(() -> {
            try {
                Thread.sleep(3000);
                System.out.println("消费者准备取出数据...");
                String data = queue.take();
                System.out.println("消费者取到:" + data);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "消费者").start();
    }
}

输出:

生产者准备放入数据...
(等待3秒...)
消费者准备取出数据...
生产者成功放入数据!
消费者取到:数据1

📊 四、实现类对比总结

队列类型底层结构容量锁机制排序应用场景
ArrayBlockingQueue数组有界(必须指定)单锁FIFO资源有限的生产消费
LinkedBlockingQueue链表有界/无界双锁FIFO高并发场景
PriorityBlockingQueue无界单锁优先级任务调度
DelayQueue优先队列无界单锁延迟时间定时任务、缓存过期
SynchronousQueue0--直接交接、线程池

🎯 五、经典应用场景

5.1 生产者-消费者模式 🏭

public class ProducerConsumerDemo {
    public static void main(String[] args) {
        BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10);
        
        // 生产者线程
        Thread producer = new Thread(() -> {
            try {
                for (int i = 1; i <= 20; i++) {
                    queue.put(i);
                    System.out.println("生产:" + i + ",队列大小:" + queue.size());
                    Thread.sleep(100);
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }, "生产者");
        
        // 消费者线程
        Thread consumer = new Thread(() -> {
            try {
                while (true) {
                    Integer item = queue.take();
                    System.out.println("  消费:" + item + ",队列大小:" + queue.size());
                    Thread.sleep(300);
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }, "消费者");
        
        producer.start();
        consumer.start();
    }
}

5.2 线程池任务队列 💼

// Executors.newFixedThreadPool 内部使用 LinkedBlockingQueue
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    5,                          // 核心线程数
    10,                         // 最大线程数
    60,                         // 空闲线程存活时间
    TimeUnit.SECONDS,
    new LinkedBlockingQueue<>(100)  // 任务队列
);

// 提交任务
for (int i = 1; i <= 50; i++) {
    final int taskId = i;
    executor.execute(() -> {
        System.out.println("执行任务:" + taskId);
    });
}

5.3 日志异步处理 📝

public class AsyncLogger {
    private static final BlockingQueue<String> logQueue = 
        new LinkedBlockingQueue<>(1000);
    
    static {
        // 启动日志消费线程
        new Thread(() -> {
            while (true) {
                try {
                    String log = logQueue.take();
                    // 写入文件或数据库
                    System.out.println("[日志] " + log);
                } catch (InterruptedException e) {
                    break;
                }
            }
        }, "日志线程").start();
    }
    
    public static void log(String message) {
        try {
            logQueue.offer(message, 1, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            System.err.println("日志队列满,丢弃日志");
        }
    }
    
    public static void main(String[] args) {
        for (int i = 1; i <= 100; i++) {
            log("用户操作 " + i);
        }
    }
}

5.4 限流器实现 🚦

public class RateLimiter {
    private final BlockingQueue<Long> queue;
    private final int maxRequests;
    private final long timeWindow;
    
    public RateLimiter(int maxRequests, long timeWindowMs) {
        this.maxRequests = maxRequests;
        this.timeWindow = timeWindowMs;
        this.queue = new LinkedBlockingQueue<>(maxRequests);
    }
    
    public boolean tryAcquire() {
        long now = System.currentTimeMillis();
        
        // 移除过期的时间戳
        while (!queue.isEmpty() && now - queue.peek() > timeWindow) {
            queue.poll();
        }
        
        // 尝试添加新请求
        return queue.offer(now);
    }
    
    public static void main(String[] args) throws InterruptedException {
        RateLimiter limiter = new RateLimiter(5, 1000);  // 每秒5个请求
        
        for (int i = 1; i <= 10; i++) {
            if (limiter.tryAcquire()) {
                System.out.println("请求" + i + ":通过");
            } else {
                System.out.println("请求" + i + ":被限流");
            }
            Thread.sleep(150);
        }
    }
}

🎓 六、经典面试题

面试题1:ArrayBlockingQueue和LinkedBlockingQueue的区别?

答案:

特性ArrayBlockingQueueLinkedBlockingQueue
底层结构数组链表
容量必须指定可选(默认无界)
锁机制单锁(读写共用)双锁(读写分离)
并发性能较低较高
内存占用预分配,固定动态分配,节点开销

面试题2:put()和offer()的区别?

答案:

  • put(e):阻塞方法,队列满时会一直等待
  • offer(e):非阻塞,队列满时返回false
  • offer(e, timeout, unit):超时阻塞,等待指定时间

面试题3:如何实现一个简单的阻塞队列?

答案:

public class SimpleBlockingQueue<T> {
    private final Queue<T> queue = new LinkedList<>();
    private final int capacity;
    
    public SimpleBlockingQueue(int capacity) {
        this.capacity = capacity;
    }
    
    public synchronized void put(T element) throws InterruptedException {
        while (queue.size() == capacity) {
            wait();  // 队列满,等待
        }
        queue.add(element);
        notifyAll();  // 通知消费者
    }
    
    public synchronized T take() throws InterruptedException {
        while (queue.isEmpty()) {
            wait();  // 队列空,等待
        }
        T element = queue.remove();
        notifyAll();  // 通知生产者
        return element;
    }
}

面试题4:BlockingQueue在线程池中的作用?

答案:

  • 存储待执行的任务
  • 解耦任务提交和任务执行
  • 控制并发数量(有界队列)
  • 实现拒绝策略

🎪 七、趣味小故事

故事:蒸笼的智慧

从前有个包子铺,老板叫张三,是个急性子。

没有阻塞队列的日子:

老板张三每天凌晨3点起床做包子,做得飞快:

  • 3点到4点:做了100个包子,堆在柜台上
  • 问题来了:柜台只能放50个,其他50个掉地上了!😱
  • 老板崩溃:"我这么努力,包子却浪费了!"

下午生意好的时候:

  • 顾客疯狂涌入,买光了所有包子
  • 老板累得直喘气,手忙脚乱现做
  • 顾客不耐烦:"这么慢,不买了!" 😤

有了阻塞队列(蒸笼):

后来,张三买了个智能蒸笼(BlockingQueue):

  • 🔹 容量固定50个
  • 🔹 满了就自动提醒:"慢点做,我满了!"
  • 🔹 空了就提醒:"快做,我空了!"

现在的工作模式:

// 老板(生产者)
while (营业中) {
    包子 = 制作包子();
    蒸笼.put(包子);  // 满了就休息,不浪费
}

// 顾客(消费者)
while (想买包子) {
    包子 = 蒸笼.take();  // 空了就等,不着急
}

结果:

  • ✅ 老板:按节奏生产,不累不浪费
  • ✅ 顾客:总有热包子,不用等太久
  • ✅ 包子铺:生意越来越好!🎉

这就是阻塞队列的魔力——让生产者和消费者完美协作!


📚 八、知识点总结

核心要点 ✨

  1. 定义:线程安全的队列,支持阻塞操作
  2. 核心方法:put()/take()阻塞,offer()/poll()非阻塞
  3. 实现类
    • ArrayBlockingQueue:有界数组
    • LinkedBlockingQueue:有界/无界链表
    • PriorityBlockingQueue:无界优先级
    • DelayQueue:延迟队列
    • SynchronousQueue:零容量同步
  4. 应用:生产者-消费者、线程池、异步处理

记忆口诀 🎵

阻塞队列真神奇,
生产消费不着急。
队列满了put会等,
队列空了take阻塞。
Array数组有界限,
Linked链表更灵活。
Priority优先级,
Delay延迟有特色。
线程安全不用锁,
并发编程好帮手!

选择建议 🎯

有界 + 高性能    → ArrayBlockingQueue
高并发 + 吞吐量  → LinkedBlockingQueue
优先级调度       → PriorityBlockingQueue
定时任务         → DelayQueue
直接交接         → SynchronousQueue

🎯 九、练习题

练习1:实现一个有界缓冲区

public class BoundedBuffer<T> {
    private final BlockingQueue<T> queue;
    
    public BoundedBuffer(int capacity) {
        this.queue = new ArrayBlockingQueue<>(capacity);
    }
    
    public void put(T item) throws InterruptedException {
        queue.put(item);
    }
    
    public T take() throws InterruptedException {
        return queue.take();
    }
}

练习2:多生产者多消费者

public class MultiProducerConsumer {
    public static void main(String[] args) {
        BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(20);
        
        // 3个生产者
        for (int i = 1; i <= 3; i++) {
            new Producer(queue, i).start();
        }
        
        // 2个消费者
        for (int i = 1; i <= 2; i++) {
            new Consumer(queue, i).start();
        }
    }
    
    static class Producer extends Thread {
        private BlockingQueue<Integer> queue;
        private int id;
        
        public Producer(BlockingQueue<Integer> queue, int id) {
            this.queue = queue;
            this.id = id;
        }
        
        @Override
        public void run() {
            try {
                for (int i = 1; i <= 10; i++) {
                    queue.put(i);
                    System.out.println("生产者" + id + " 生产:" + i);
                    Thread.sleep(200);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    static class Consumer extends Thread {
        private BlockingQueue<Integer> queue;
        private int id;
        
        public Consumer(BlockingQueue<Integer> queue, int id) {
            this.queue = queue;
            this.id = id;
        }
        
        @Override
        public void run() {
            try {
                while (true) {
                    Integer item = queue.take();
                    System.out.println("  消费者" + id + " 消费:" + item);
                    Thread.sleep(300);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

🌟 十、总结彩蛋

恭喜你!🎉 你已经掌握了阻塞队列这个并发编程的利器!

记住:

  • 🔒 阻塞队列 = 线程安全 + 自动阻塞
  • 🎯 put()/take() 是最常用的阻塞方法
  • 💪 生产者-消费者模式的完美解决方案
  • 🚀 不同实现类适用不同场景

最后送你一张图

    生产者               消费者
       ↓                   ↑
       📦 → [阻塞队列] → 📦
              ↓
          自动协调
        ✅ 满了等
        ✅ 空了等
        ✅ 完美平衡

下次见,继续学习下一个知识点吧! 💪😄


📖 参考资料

  1. Java官方文档:BlockingQueue
  2. 《Java并发编程实战》第5章
  3. 《Java并发编程的艺术》- 阻塞队列
  4. Doug Lea:《Java并发编程:设计原则与模式》

作者: AI算法导师
最后更新: 2025年11月
难度等级: ⭐⭐⭐⭐ (中高级)
预计学习时间: 3-4小时

💡 温馨提示:阻塞队列是并发编程的基础,理解它的阻塞机制和使用场景非常重要!