生产者消费者模型
在并发编程中 使用生产者和消费者模式能够解决绝大多数并发问题。
该模式通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度。
1.什么是生产者消费者模式?
生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。
2.为什么要使用生产者和消费者模式?
在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。
在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。
如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。
为了解决这个问题,就在生产者和消费者之间通过队列,增加缓冲,避免了生产者和消费者 之间的交互。于是引入了生产者和消费者模式。
一、示例准备
在分布式和微服务架构中,生产消费模式是一种常见的模式,通常用于解耦系统、提高系统的吞吐量和响应速度。本示例将演示如何使用Spring Boot框架,结合线程池、异步执行、MyBatis和MySQL数据库,实现一个每1秒生产1000条数据,而消费者每10秒批量存储一次的生产消费模式。
二、环境准备
Spring Boot: 快速搭建Web应用的基础框架。
MyBatis: 优秀的持久层框架,支持定制化SQL、存储过程以及高级映射。
MySQL: 关系型数据库管理系统。
三、项目结构
生产者(Producer):负责数据的生产,每1秒生成1000条数据,并将数据放入队列或缓冲区。
消费者(Consumer):负责从队列或缓冲区中取出数据,每10秒批量存储到MySQL数据库。
线程池(ThreadPool):用于异步执行消费者的存储操作。
四、实现步骤
1. 创建Spring Boot项目
使用Spring Initializr(https://start.spring.io/)创建一个新的Spring Boot项目,添加必要的依赖spring-boot-starter-web、mybatis-spring-boot-starter、mysql-connector-java等。
2. 配置数据库连接
在application.properties中配置MySQL数据库连接信息。
spring.datasource.url=jdbc:mysql://localhost:3306/weijishu_db
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
3. 创建数据模型(Entity)和Mapper
根据业务需求创建数据模型和对应的MyBatis Mapper接口。
4. 实现生产者(Producer)
创建一个生产者类,使用@Scheduled注解实现每1秒执行一次的定时任务,生成数据并放入队列。
@Component
public class Producer {
@Autowired
private BlockingQueue<Data> dataQueue;
@Scheduled(fixedRate = 1000) // 每1秒执行一次
public void produceData() {
for (int i = 0; i < 1000; i++) {
Data data = new Data(); // 假设Data是数据模型
// ... 初始化数据
dataQueue.offer(data);
}
}
}
5. 实现消费者(Consumer)
使用@Async注解实现异步执行,并使用线程池。消费者从队列中取出数据,并每10秒批量存储。
@Component
public class Consumer {
@Autowired
private DataMapper dataMapper; // 假设DataMapper是MyBatis的Mapper接口
@Autowired
private ThreadPoolTaskExecutor taskExecutor; // 注入线程池
@Scheduled(fixedDelay = 10000) // 每10秒执行一次
public void consumeData() {
List<Data> dataList = new ArrayList<>();
Data data;
while ((data = dataQueue.poll()) != null) {
dataList.add(data);
if (dataList.size() >= 1000 || dataQueue.isEmpty()) { // 可以设置其他批量条件
taskExecutor.execute(() -> {
// 异步批量存储
dataMapper.insertDataList(dataList);
dataList.clear(); // 清空列表以便下次使用
});
}
}
}
}
注意:在DataMapper中需要实现insertDataList方法,用于批量插入数据。
6. 配置线程池(ThreadPool)
在Spring Boot的配置类中配置线程池。
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean(name = "taskExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5); // 核心线程数
executor.setMaxPoolSize(10); // 最大线程数
executor.setQueueCapacity(25); // 队列容量
executor.setKeepAliveSeconds(60); // 线程空闲时间
executor.setThreadNamePrefix("Async-Service-"); // 线程名称前缀
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); // 拒绝策略
executor.initialize();
return executor;
}
}
注意:@EnableAsync注解启用异步方法执行。
7. 启动类
启动类上添加@SpringBootApplication注解和@EnableScheduling注解来启用定时任务。
@SpringBootApplication
@EnableScheduling
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
五、测试与验证
启动Spring Boot应用。
检查MySQL数据库,确保数据表结构已经创建好。
等待一段时间后,检查数据库中的数据,验证是否每10秒批量插入了数据。
六、说明
本示例演示了如何使用Spring Boot结合MyBatis、MySQL、线程池和异步执行实现生产消费模式。生产者通过定时任务每1秒生成1000条数据并放入队列,消费者则每10秒从队列中取出数据并批量存储到MySQL数据库中。使用线程池和异步执行可以提高系统的吞吐量和响应速度。
在实际应用中,可能还需要考虑数据的去重、错误处理、队列的容量管理、事务处理等问题,以确保系统的稳定性和可用性。此外,也可以考虑使用消息中间件(如RabbitMQ、Kafka等)来实现更可靠、更灵活的生产消费模式。