- Java 并发、BlockingQueue、生产者消费者模式、线程池、LinkedBlockingQueue、批量消费
- 线程安全 · 零消息丢失 · 优雅退出 · 批量消费优化
一、场景需求
假设我们需要:
- 启动 10 个生产者线程,每个生产 1000 条结构化消息(Map<String, String>);
- 由 1 个消费者线程 批量拉取消息并模拟持久化(如数据库写入);
- 确保 不丢失任何消息,且程序能 优雅退出。
- 这正是典型的“日志收集”、“事件处理”或“异步任务队列”场景。
二、核心组件选择
- 阻塞队列:LinkedBlockingQueue
- 线程安全,支持 put()(阻塞)和 drainTo()(批量拉取);
- 默认容量为 Integer.MAX_VALUE(≈21亿),逻辑上“无界”;
- 适合生产速度波动较大的场景(但需警惕 OOM,后文会优化)。
- 线程池
- Executors.newFixedThreadPool(10):固定 10 个生产者线程;
- Executors.newSingleThreadExecutor():单消费者线程,保证顺序处理。
- 同步标志
- AtomicBoolean allProducersDone:通知消费者“生产已结束”;
- AtomicInteger producedCount:统计总生产量(可选)。
三、完整实现代码
package oscollege.back;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 多生产者-单消费者模型示例
*
* 场景说明:
* - 10个生产者线程,每个生产1000条消息(共10,000条)
* - 1个消费者线程,批量拉取消息并模拟处理(如数据库写入)
* - 使用阻塞队列实现线程安全的消息传递
* - 程序能确保不丢失消息,并优雅退出
*/
public class Temp {
public static void main(String[] args) throws InterruptedException {
// 创建固定大小的线程池:10个生产者线程
ExecutorService producerPool = Executors.newFixedThreadPool(10);
// 创建单线程消费者线程池:保证消息按批次顺序处理
ExecutorService consumerPool = Executors.newSingleThreadExecutor();
// ⚠️ 注意:LinkedBlockingQueue() 默认容量为 Integer.MAX_VALUE(≈21亿),属于“逻辑无界”队列
// 虽然方便,但存在内存溢出(OOM)风险。生产环境建议指定容量,例如 new LinkedBlockingQueue<>(10_000)
BlockingQueue<Map<String, String>> mapBlockingQueue = new LinkedBlockingQueue<>();
// 原子布尔值:用于通知消费者“所有生产者已完成工作”
// 必须使用 volatile 或原子类,确保多线程可见性
AtomicBoolean allProducersDone = new AtomicBoolean(false);
// 原子整数:统计总共生产了多少条消息(仅用于日志输出,非核心逻辑)
AtomicInteger producedCount = new AtomicInteger(0);
// ========================
// 启动10个生产者线程
// ========================
final int totalMessagesPerProducer = 1000; // 每个生产者生产1000条消息
for (int i = 0; i < 10; i++) {
final int producerId = i; // 捕获循环变量(Lambda 表达式要求)
producerPool.submit(() -> {
try {
for (int j = 0; j < totalMessagesPerProducer; j++) {
// 构造一条模拟消息(实际可能是日志、事件、订单等)
Map<String, String> message = new HashMap<>();
message.put("producer", String.valueOf(producerId));
message.put("seq", String.valueOf(j));
message.put("data", "msg-" + producerId + "-" + j);
// 使用 put() 方法:队列满时会阻塞当前线程,直到有空间
// 这样可以确保消息不会丢失(与 offer() 的非阻塞行为不同)
mapBlockingQueue.put(message);
// 统计总生产量(仅用于日志)
producedCount.incrementAndGet();
}
} catch (InterruptedException e) {
// 恢复中断状态
Thread.currentThread().interrupt();
}
// 生产者任务自然结束
});
}
// ========================
// 启动消费者线程(批量消费)
// ========================
consumerPool.submit(() -> {
// 重用 List 对象以减少内存分配(避免频繁创建新 ArrayList)
List<Map<String, String>> batch = new ArrayList<>();
try {
// 持续循环,直到所有消息处理完毕
while (true) {
// 批量从队列中拉取消息,最多拉取100条
// drainTo 是高效操作:一次性转移元素,减少锁竞争
int drained = mapBlockingQueue.drainTo(batch, 100);
// 只有当实际拉取到消息时,才进行处理(避免空处理)
if (drained > 0) {
System.out.println("📦 消费者批量处理 " + drained + " 条消息");
// 模拟耗时的 I/O 操作,例如:数据库批量插入、网络请求等
// 实际项目中应替换为真实业务逻辑
Thread.sleep(1000);
// 清空批次列表,供下一次复用
batch.clear();
}
// ========== 安全退出判断 ==========
// 条件1: allProducersDone == true → 所有生产者已结束,不会再有新消息
// 条件2: 队列当前为空 → 没有剩余消息待处理
// 两个条件同时满足时,可安全退出消费循环
//
// 💡 为什么安全?
// 因为 allProducersDone 是在 producerPool.awaitTermination() 成功后才设置为 true,
// 此时所有生产者线程已完全终止,不可能再往队列中添加新消息。
// 所以一旦队列变空,就真的“永远空”了。
if (allProducersDone.get() && mapBlockingQueue.isEmpty()) {
System.out.println("✅ 消费者已完成所有消息处理");
break; // 退出消费循环
}
}
} catch (InterruptedException e) {
// 恢复线程中断状态(良好实践)
Thread.currentThread().interrupt();
System.err.println("消费者被中断");
}
});
// ========================
// 等待所有生产者完成
// ========================
// 关闭生产者线程池:不再接受新任务,但已提交的任务会继续执行
producerPool.shutdown();
// 阻塞等待最多5分钟,直到所有生产者任务完成
boolean producersFinished = producerPool.awaitTermination(5, TimeUnit.MINUTES);
if (!producersFinished) {
// 超时未完成,强制终止所有生产者线程
System.err.println("⚠️ 生产者超时,强制关闭");
producerPool.shutdownNow(); // 中断正在执行的任务
}
// 关键步骤:标记“所有生产者已完成”
// 此时可以确保不会再有新消息入队
allProducersDone.set(true);
System.out.println("✅ 所有生产者已完成,共生产 " + producedCount.get() + " 条消息");
// ========================
// 等待消费者完成剩余消息处理
// ========================
// 关闭消费者线程池
consumerPool.shutdown();
// 等待消费者最多10分钟完成处理
boolean consumerFinished = consumerPool.awaitTermination(10, TimeUnit.MINUTES);
if (!consumerFinished) {
System.err.println("⚠️ 消费者未在预期时间内完成,强制关闭");
consumerPool.shutdownNow();
} else {
System.out.println("✅ 消费者已成功处理所有消息");
}
System.out.println("🏁 主程序结束");
}
}


被折叠的 条评论
为什么被折叠?



