Java并发:JDK17下多生产者+单消费者的高性能消息处理模式

该文章已生成可运行项目,
  • Java 并发、BlockingQueue、生产者消费者模式、线程池、LinkedBlockingQueue、批量消费
  • 线程安全 · 零消息丢失 · 优雅退出 · 批量消费优化

一、场景需求

假设我们需要:

  • 启动 10 个生产者线程,每个生产 1000 条结构化消息(Map<String, String>);
  • 由 1 个消费者线程 批量拉取消息并模拟持久化(如数据库写入);
  • 确保 不丢失任何消息,且程序能 优雅退出。
  • 这正是典型的“日志收集”、“事件处理”或“异步任务队列”场景。

二、核心组件选择

  1. 阻塞队列:LinkedBlockingQueue
  • 线程安全,支持 put()(阻塞)和 drainTo()(批量拉取);
  • 默认容量为 Integer.MAX_VALUE(≈21亿),逻辑上“无界”;
  • 适合生产速度波动较大的场景(但需警惕 OOM,后文会优化)。
  1. 线程池
  • Executors.newFixedThreadPool(10):固定 10 个生产者线程;
  • Executors.newSingleThreadExecutor():单消费者线程,保证顺序处理。
  1. 同步标志
  • 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("🏁 主程序结束");
    }
}

本文章已经生成可运行项目
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

孟浩浩

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值