三种消费者生产者模式写法

本文探讨了生产者消费者模式在多线程环境下的实现,通过三种不同的方式:使用synchronized关键字、ReentrantLock锁和Condition条件变量、以及使用LinkedBlockingQueue阻塞队列,展示了如何在Java中有效协调生产者和消费者之间的数据交换。
package ConcurrentSource;/*
@author 黄大宁Rhinos
@date 2019/5/12 - 22:33
**/

import java.util.LinkedList;
import java.util.Queue;

public class CustomerAndProducer01 {
    private Queue<Integer> queue;
    private int maxSize;
    CustomerAndProducer01(int maxSize){
        this.queue = new LinkedList<Integer>();
        this.maxSize = maxSize;
    }
    class Customer implements Runnable{
        public void use(){
            synchronized (queue){
                while(queue.size()==0){
                    try {
                        queue.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                queue.poll();
                System.out.println(Thread.currentThread().getName() + "消费后还有"+queue.size());
                queue.notifyAll();
            }
        }
        @Override
        public void run() {
            for(int i=0;i<5;i++){
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                use();
            }
        }
    }
    class Producer implements Runnable{
        public void produce(){
            synchronized (queue){
                while(queue.size()==maxSize){
                    try {
                        queue.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                queue.offer(1);
                System.out.println(Thread.currentThread().getName() +"生产后还有"+queue.size());
                queue.notifyAll();
            }
        }
        @Override
        public void run() {
            for(int i=0;i<5;i++){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                produce();
            }

        }
    }

    public static void main(String[] args) {
        CustomerAndProducer01 cp = new CustomerAndProducer01(10);
        for(int i=0;i<10;i++){
            new Thread(cp.new Customer()).start();
        }
        for(int i=0;i<5;i++){
            new Thread(cp.new Producer()).start();
        }
    }
}


package ConcurrentSource;/*
@author 黄大宁Rhinos
@date 2019/5/12 - 23:18
**/

import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class CustomerAndProducer02 {
    private Queue<Integer> queue;
    private int maxSize;
    private Lock lock;
    private Condition isFull;
    private Condition isEmpty;
    CustomerAndProducer02(int maxSize){
        this.queue = new LinkedList<Integer>();
        this.maxSize = maxSize;
        this.lock = new ReentrantLock();
        isEmpty = lock.newCondition();
        isFull = lock.newCondition();
    }
    class Customer implements Runnable{
        public void use(){
            lock.lock();
            try{
                while(queue.size()==0){
                    try {
                        isEmpty.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                queue.poll();
                System.out.println(Thread.currentThread().getName() + "消费后还有"+queue.size());
                isFull.signalAll();
            }finally {
                lock.unlock();
            }
        }
        @Override
        public void run() {
            for(int i=0;i<5;i++){
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                use();
            }
        }
    }
    class Producer implements Runnable{
        public void produce(){
            lock.lock();
            try{
                while(queue.size()==maxSize){
                    try {
                        isFull.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                queue.offer(1);
                System.out.println(Thread.currentThread().getName() + "消费后还有"+queue.size());
                isEmpty.signalAll();
            }finally {
                lock.unlock();
            }
        }
        @Override
        public void run() {
            for(int i=0;i<5;i++){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                produce();
            }

        }
    }

    public static void main(String[] args) {
        CustomerAndProducer02 cp = new CustomerAndProducer02(10);
        for(int i=0;i<10;i++){
            new Thread(cp.new Customer()).start();
        }
        for(int i=0;i<5;i++){
            new Thread(cp.new Producer()).start();
        }
    }
}

package ConcurrentSource;/*
@author 黄大宁Rhinos
@date 2019/5/12 - 23:27
**/

import java.util.concurrent.LinkedBlockingQueue;


public class CustomerAndProducer03 {
    private LinkedBlockingQueue queue;
    private int maxSize;
    CustomerAndProducer03(int maxSize){
        this.queue = new LinkedBlockingQueue(maxSize);
        this.maxSize = maxSize;
    }

    class Customer implements Runnable{
        @Override
        public void run() {
            for(int i=0;i<5;i++){
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                try {
                    queue.take();
                    System.out.println(Thread.currentThread().getName() + "消费后还有"+queue.size());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    class Producer implements Runnable{
        @Override
        public void run() {
            for(int i=0;i<5;i++){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                try {
                    queue.put(1);
                    System.out.println(Thread.currentThread().getName() + "生产后还有"+queue.size());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }
    }
    public static void main(String[] args) {
        CustomerAndProducer03 cp = new CustomerAndProducer03(10);
        for(int i=0;i<10;i++){
            new Thread(cp.new Customer()).start();
        }
        for(int i=0;i<5;i++){
            new Thread(cp.new Producer()).start();
        }
    }
}

<think>根据用户需求,需要在Spring MVC中通过XML配置整合RabbitMQ,实现生产者发送Map消息,消费者通过实现MessageListener接口接收Map。下面将分步骤详细说明配置过程。 ### 🔧 整体方案 1. **生产者端**:使用`RabbitTemplate`发送Map消息,配置JSON消息转换器 2. **消费者端**:实现`MessageListener`接口,手动解析JSON到Map 3. **XML配置**:声明连接工厂、模板、监听容器等Bean ### 📝 实现步骤 #### 1. 生产者发送Map消息(Java代码) ```java import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.Map; @Service public class MapMessageProducer { @Autowired private RabbitTemplate rabbitTemplate; public void sendMapMessage(String exchange, String routingKey, Map<String, Object> map) { // 使用convertAndSend发送,JSON转换器会自动转换Map为JSON rabbitTemplate.convertAndSend(exchange, routingKey, map); } } ``` #### 2. 消费者实现MessageListener接口 ```java import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.amqp.core.Message; import org.springframework.amqp.core.MessageListener; import java.util.Map; public class MapMessageListener implements MessageListener { private static final ObjectMapper objectMapper = new ObjectMapper(); @Override public void onMessage(Message message) { try { // 1. 获取消息字节数组 byte[] body = message.getBody(); // 2. 手动转换为Map类型(关键步骤) Map<String, Object> map = objectMapper.readValue( body, objectMapper.getTypeFactory().constructMapType(Map.class, String.class, Object.class) ); // 3. 处理业务逻辑 System.out.println("收到Map消息: " + map); String name = (String) map.get("name"); Integer age = (Integer) map.get("age"); System.out.println("姓名: " + name + ", 年龄: " + age); } catch (Exception e) { e.printStackTrace(); // 注意:这里无法进行消息确认(因为MessageListener没有提供Channel) // 需要配置AcknowledgeMode为AUTO(自动确认)或使用ChannelAwareMessageListener } } } ``` > ⚠️ 注意:普通`MessageListener`接口无法进行消息确认(ACK/NACK),如果需要手动确认,需改为实现`ChannelAwareMessageListener`接口(参考后续优化建议)。 #### 3. XML核心配置(spring-rabbit.xml) ```xml <!-- 1. 连接工厂 --> <rabbit:connection-factory id="connectionFactory" host="localhost" port="5672" username="guest" password="guest" virtual-host="/"/> <!-- 2. JSON消息转换器 --> <bean id="jsonMessageConverter" class="org.springframework.amqp.support.converter.Jackson2JsonMessageConverter"/> <!-- 3. Rabbit模板 --> <rabbit:template id="rabbitTemplate" connection-factory="connectionFactory" message-converter="jsonMessageConverter"/> <!-- 关键:配置JSON转换器 --> <!-- 4. 声明队列 --> <rabbit:queue name="mapMessageQueue" durable="true"/> <!-- 5. 监听容器配置 --> <bean id="mapListener" class="com.example.MapMessageListener"/> <rabbit:listener-container connection-factory="connectionFactory" acknowledge="auto"> <!-- 自动确认模式 --> <!-- 使用普通MessageListener需设置为自动确认 --> <rabbit:listener ref="mapListener" queues="mapMessageQueue"/> </rabbit:listener-container> ``` ### 🔍 关键配置解析 1. **JSON消息转换器**: 生产者端`Jackson2JsonMessageConverter`会将Map自动转为JSON发送,消费者端需手动转换[^1]。 2. **消息确认模式**: 由于`MessageListener`没有提供Channel参数,只能使用: - `acknowledge="auto"`:自动确认(默认,消息到达消费者即删除) - `acknowledge="none"`:不确认(不推荐) 3. **转换类型安全**: 消费者使用`constructMapType`明确指定Map的键值类型,避免泛型擦除问题: ```java // 正确写法(指定键为String,值为Object) constructMapType(Map.class, String.class, Object.class) ``` ### ⚠️ 注意事项 1. **消费者局限性**: 普通`MessageListener`无法手动ACK,可能导致: - 消息处理失败时仍被删除(自动ACK) - 无法控制消息重试 2. **改进方案**: 如需手动确认,改为实现`ChannelAwareMessageListener`: ```java public class MapMessageListener implements ChannelAwareMessageListener { @Override public void onMessage(Message message, Channel channel) throws Exception { long deliveryTag = message.getMessageProperties().getDeliveryTag(); try { // 转换和处理逻辑... channel.basicAck(deliveryTag, false); // 手动确认 } catch (Exception e) { channel.basicNack(deliveryTag, false, true); // 拒绝并重新入队 } } } ``` 对应XML需配置`acknowledge="manual"`: ```xml <rabbit:listener-container acknowledge="manual"> <!-- ... --> </rabbit:listener-container> ``` 3. **生产者发送验证**: 发送前检查Map内容: ```java Map<String, Object> map = new HashMap<>(); map.put("name", "张三"); map.put("age", 18); producer.sendMapMessage("ex", "route.key", map); ``` ### 💡 优化建议 1. **消费者泛型封装**: 创建工具类统一处理JSON转换: ```java public class MessageConverterUtil { private static final ObjectMapper mapper = new ObjectMapper(); public static <T> T fromMessage(Message message, Class<T> clazz) { return mapper.readValue(message.getBody(), clazz); } public static Map<String, Object> toMap(Message message) { return mapper.readValue(message.getBody(), mapper.getTypeFactory().constructMapType(Map.class, String.class, Object.class)); } } ``` 2. **添加消息类型校验**: 在消费者中检查消息头确保是JSON: ```java String contentType = message.getMessageProperties().getContentType(); if (!"application/json".equals(contentType)) { throw new IllegalArgumentException("非JSON消息"); } ``` ### 📌 总结配置要点 | 组件 | 配置项 | 值 | 作用 | |------|--------|----|------| | 生产者 | message-converter | Jackson2JsonMessageConverter | 自动转换Map为JSON | | 消费者 | 接口选择 | MessageListener | 最简单监听方式 | | | 消息转换 | 手动ObjectMapper解析 | JSON转Map | | XML容器 | acknowledge | auto | 自动确认消息 | > 通过此方案,生产者能发送Map消息,消费者通过实现`MessageListener`接口接收Map,但需注意**自动确认模式的风险**。对于需要可靠处理的场景,强烈建议升级为`ChannelAwareMessageListener`[^1][^3]。 --- ### 相关问题 1. **如何为不同的队列配置不同的消息监听器?** `在XML中声明多个<rabbit:listener-container>,每个容器绑定专属监听器和队列` 2. **手动ACK模式下如何避免消息积压?** `需保证业务处理成功率,实现失败重试机制,并设置合理的prefetchCount限制未ACK消息数量` 3. **如何验证生产者发送的消息格式?** `启用RabbitMQ管理插件,通过Web控制台查看消息详情,或使用WireShark抓取AMQP协议数据包` 4. **消费者处理Map消息时如何应对字段缺失?** `转换后使用map.containsKey("key")检查,或封装为POJO类配合@JsonIgnoreProperties(ignoreUnknown=true)` 5. **XML配置与注解配置哪种更适合Spring MVC项目?** `XML配置集中管理优势明显,适合传统Spring MVC项目;注解配置更简洁,推荐Spring Boot项目使用`</think>在 Spring MVC 中通过 XML 配置实现 RabbitMQ 生产者消费者,处理 Map 类型消息的完整方案如下: ### 一、生产者配置(发送 Map 消息) #### 1. 生产者 Java 代码 ```java import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.factory.annotation.Autowired; import java.util.Map; @Service public class MapProducer { @Autowired private RabbitTemplate rabbitTemplate; public void sendMapMessage(String exchange, String routingKey, Map<String, Object> mapData) { // 发送 Map 类型消息 rabbitTemplate.convertAndSend(exchange, routingKey, mapData); } } ``` #### 2. XML 生产者配置(spring-rabbit-producer.xml) ```xml <!-- 1. 连接工厂 --> <rabbit:connection-factory id="connectionFactory" host="localhost" port="5672" username="guest" password="guest" virtual-host="/"/> <!-- 2. JSON消息转换器 --> <bean id="jsonMessageConverter" class="org.springframework.amqp.support.converter.Jackson2JsonMessageConverter"/> <!-- 3. Rabbit模板 --> <rabbit:template id="rabbitTemplate" connection-factory="connectionFactory" message-converter="jsonMessageConverter"/> <!-- 关键配置:JSON转换器 --> <!-- 4. 声明交换机和队列 --> <rabbit:direct-exchange name="mapExchange"> <rabbit:bindings> <rabbit:binding queue="mapQueue" key="map.key"/> </rabbit:bindings> </rabbit:direct-exchange> <rabbit:queue name="mapQueue" durable="true"/> ``` > **关键点**:必须配置 `Jackson2JsonMessageConverter` 才能正确处理 Map 类型[^1] ### 二、消费者配置(实现 MessageListener 接收 Map) #### 1. 消费者 Java 代码 ```java import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.amqp.core.Message; import org.springframework.amqp.core.MessageListener; import java.util.Map; public class MapMessageListener implements MessageListener { private static final ObjectMapper objectMapper = new ObjectMapper(); @Override public void onMessage(Message message) { try { // 1. 获取原始消息字节数组 byte[] body = message.getBody(); // 2. 手动转换为Map类型 Map<String, Object> map = objectMapper.readValue( body, objectMapper.getTypeFactory().constructMapType(Map.class, String.class, Object.class) ); // 3. 处理业务逻辑 System.out.println("收到Map消息: " + map); String name = (String) map.get("name"); Integer age = (Integer) map.get("age"); System.out.println("姓名: " + name + ", 年龄: " + age); } catch (Exception e) { e.printStackTrace(); // 注意:普通MessageListener无法进行消息确认 } } } ``` #### 2. XML 消费者配置(spring-rabbit-consumer.xml) ```xml <!-- 复用连接工厂 --> <rabbit:connection-factory id="connectionFactory".../> <!-- 声明监听容器 --> <rabbit:listener-container connection-factory="connectionFactory" acknowledge="auto"> <!-- 自动确认模式 --> <rabbit:listener ref="mapMessageListener" queues="mapQueue"/> </rabbit:listener-container> <!-- 注册监听器 --> <bean id="mapMessageListener" class="com.example.MapMessageListener"/> ``` ### 三、测试用例 #### 1. 生产者测试 ```java @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:spring-rabbit-producer.xml") public class MapProducerTest { @Autowired private MapProducer mapProducer; @Test public void testSendMap() { Map<String, Object> userMap = new HashMap<>(); userMap.put("name", "张三"); userMap.put("age", 25); userMap.put("email", "zhangsan@example.com"); mapProducer.sendMapMessage("mapExchange", "map.key", userMap); } } ``` #### 2. 消费者输出结果 ``` 收到Map消息: {name=张三, age=25, email=zhangsan@example.com} 姓名: 张三, 年龄: 25 ``` ### 四、配置关键点说明 | 配置项 | 作用 | 必要性 | |--------|------|--------| | `Jackson2JsonMessageConverter` | 将Map转换为JSON | 必须配置 | | `constructMapType()` | 明确Map的键值类型 | 防止类型转换错误 | | `acknowledge="auto"` | 自动确认消息 | 普通MessageListener必需 | | `durable="true"` | 队列持久化 | 防止消息丢失 | ### 五、常见问题解决 **问题1:消费者收到消息但无法解析为Map** 原因:生产者未正确配置 JSON 转换器 解决:确保生产者 XML 配置了: ```xml <bean id="jsonMessageConverter" class="org.springframework.amqp.support.converter.Jackson2JsonMessageConverter"/> <rabbit:template message-converter="jsonMessageConverter"/> ``` **问题2:消费者报类型转换错误** 原因:未明确指定 Map 的键值类型 解决:使用类型安全的转换方法: ```java // 错误:直接使用Map.class Map map = objectMapper.readValue(body, Map.class); // 正确:指定键值类型 Map<String, Object> map = objectMapper.readValue( body, objectMapper.getTypeFactory().constructMapType(Map.class, String.class, Object.class) ); ``` **问题3:需要手动确认消息** 方案:改用 `ChannelAwareMessageListener` 接口: ```java public class AdvancedMapListener implements ChannelAwareMessageListener { @Override public void onMessage(Message message, Channel channel) throws Exception { // ... 转换逻辑 ... channel.basicAck(deliveryTag, false); // 手动确认 } } ``` XML 需修改: ```xml <rabbit:listener-container acknowledge="manual"> <rabbit:listener .../> </rabbit:listener-container> ``` ### 六、完整配置结构 ``` src/main/resources ├── spring-mvc.xml ├── spring-rabbit-producer.xml <!-- 生产者配置 --> ├── spring-rabbit-consumer.xml <!-- 消费者配置 --> └── rabbit-queues.xml <!-- 队列声明 --> ``` > **最佳实践**:对于复杂系统,建议将生产者/消费者配置分离,队列声明单独放在 `rabbit-queues.xml` 中[^2][^3] --- ### 相关问题 1. **如何为不同的队列配置不同的消息转换器?** `需要创建多个ListenerContainer,每个容器绑定专属MessageConverter:<rabbit:listener-container message-converter="specificConverter">` 2. **如何实现消费者手动确认消息?** `需改用ChannelAwareMessageListener接口,并在XML中设置acknowledge="manual"` 3. **生产者消费者不在同一项目如何配置?** `生产者只需配置RabbitTemplate和JSON转换器,消费者需声明队列并配置监听器` 4. **如何处理消费者端的异常?** `普通MessageListener需在代码中捕获异常,ChannelAwareMessageListener可通过basicNack控制重试` 5. **为什么推荐JSON而不是Java原生序列化?** `JSON具有跨语言特性,避免Java版本兼容问题,且能通过字段过滤提升安全性[^1]`
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值