Spring Boot3 中如何巧用线程池实现百万数据高效插入
mhr18 2025-05-14 14:56 4 浏览 0 评论
在当今数字化浪潮中,数据量如同汹涌澎湃的潮水般急剧增长。对于互联网大厂的后端开发人员而言,处理海量数据的插入操作是一项极为常见却又极具挑战的任务。当面对百万级甚至更庞大的数据量时,传统的逐个插入方式就如同老牛拉破车,性能瓶颈尽显,系统响应迟缓,甚至可能出现超时或崩溃的状况。而 Spring Boot3 中的线程池技术,恰似一把利剑,为我们劈开了这一困境。
Spring Boot 线程池初窥
Spring Boot 是一个基于 Spring 框架的微服务开发框架,它提供了许多开箱即用的功能和简化配置的机制。在 Spring Boot 应用程序中,我们可以通过配置 ThreadPoolTaskExecutor 来创建一个线程池,用于执行批量插入任务。
比如,在配置类中创建一个 ThreadPoolTaskExecutor,并设置相应的属性:
@Configuration
public class ExecutorConfig {
@Value("${spring.task.executor.core-pool-size}")
private int corePoolSize;
@Value("${spring.task.executor.max-pool-size}")
private int maxPoolSize;
@Value("${spring.task.executor.queue-capacity}")
private int queueCapacity;
@Bean
public ThreadPoolTaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(corePoolSize);
executor.setMaxPoolSize(maxPoolSize);
executor.setQueueCapacity(queueCapacity);
return executor;
}
}
在这个配置中,我们设置了核心线程数为corePoolSize,最大线程数为maxPoolSize,队列容量为queueCapacity。这些参数可以根据实际需求进行调整。例如,如果你的服务器 CPU 资源充足,且数据插入任务较为密集,可以适当增大核心线程数和最大线程数,以充分利用 CPU 多核性能;而如果数据插入任务的突发性较强,队列容量则可以设置得大一些,以应对短时间内大量的任务请求。
使用 Java 并发编程进行批量插入
在 Java 中,我们可以使用ExecutorService接口来执行并发任务。在 Spring Boot 应用程序中,我们通过注入ThreadPoolTaskExecutor实例来实现这个功能。接下来,创建一个名为BatchDataService的服务类,用于执行批量插入任务:
@Service
public class BatchDataService {
@Autowired
private ThreadPoolTaskExecutor taskExecutor;
@Autowired
private JdbcTemplate jdbcTemplate;
public void batchInsertData(List<DataEntity> dataList) {
int batchSize = 1000; // 每批插入的数量
List<Future<?>> futures = new ArrayList<>();
for (int i = 0; i < dataList.size(); i += batchSize) {
List<DataEntity> subList = dataList.subList(i, Math.min(i + batchSize, dataList.size()));
futures.add(taskExecutor.submit(new Callable<Void>() {
@Override
public Void call() throws Exception {
jdbcTemplate.batchUpdate("INSERT INTO table_name (column1, column2) VALUES (?,?)", subList, batchSize, new PreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
DataEntity data = subList.get(i);
ps.setString(1, data.getColumn1());
ps.setString(2, data.getColumn2());
}
});
return null;
}
}));
}
for (Future<?> future : futures) {
try {
future.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
}
在这个服务类中,我们首先创建了一个线程池taskExecutor,用于执行批量插入任务。然后,我们遍历数据列表,并为每个数据创建一个任务,该任务将执行批量插入操作。这里将数据分成每批 1000 条进行插入,这是一个常见的优化策略,因为一次性插入过多数据可能会导致数据库连接资源紧张,而每批数量太少又会增加线程调度和数据库交互的开销。最后,我们调用shutdown方法来关闭线程池,确保所有任务执行完毕后资源得到释放。
传统线程池的痛点与虚拟线程的曙光
传统线程池在高并发场景下存在诸多痛点。每个平台线程绑定一个操作系统线程,当创建数千线程时,内存占用极高,单线程约 1MB 栈内存,极易触发 OOM(内存溢出);线程频繁切换导致上下文切换成本高,CPU 利用率下降,实测在 10 万请求下传统线程延迟增加 40%;而且在 I/O 阻塞时线程被挂起,无法快速释放资源处理新请求,例如在静态文件服务器场景吞吐量会下降 30%。
而 Spring Boot 3.2 引入的虚拟线程为我们带来了新的希望。配置虚拟线程也并不复杂,首先在application.properties文件中进行相关配置:
spring.threads.virtual.enabled=true # 核心开关
server.tomcat.threads.max=200 # 传统线程数限制(虚拟线程不受此限制)
同时,若使用 Tomcat 执行器,还可进行如下配置(可选):
@Bean
public TomcatProtocolHandlerCustomizer<?> protocolHandlerVirtualThreadExecutorCustomizer() {
return handler -> handler.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
}
需要注意的是,要确保 Maven/Gradle 中已添加--enable-preview参数,因为虚拟线程目前处于预览阶段。并且要避免线程池配置冲突,例如检查是否误用@Async或其他线程池注解,若有则需移除或显式指定虚拟线程执行器。若出现版本兼容问题,可升级 Spring Boot 至 3.2.1+,并确认无低版本依赖冲突(如旧版 Netty/Redis 客户端)。
实战案例与性能对比
曾经有这样一个项目,需要将 2000003 条数据插入数据库。起初采用单线程插入,耗时长达 5.75 分钟。后来利用ThreadPoolTaskExecutor多线程批量插入,开启 30 个线程,每 100 条数据插入开一个线程,最终耗时仅 1.67 分钟,效率大幅提升。通过对不同线程数的测试,发现并非线程数越多越好,网上有一个不成文的算法可用于参考确定合适的线程数。同时,在数据完整性和准确性方面,通过 sql 语句检查,未发现重复入库的问题,多线程录入数据完整。
在实际应用中,我们还可以根据具体的业务需求和性能要求,对代码进行进一步的优化和调整。例如,合理设置线程池参数、优化数据库表结构和索引、采用批量事务处理等方式,都能进一步提升数据插入的效率和稳定性。
Spring Boot3 中的线程池技术,无论是传统的 ThreadPoolTaskExecutor,还是新兴的虚拟线程,都为我们处理百万数据插入操作提供了强大的工具。只要我们根据实际场景合理运用,不断优化,就能在海量数据处理的战场上所向披靡,构建出高效、稳定的应用系统,为业务的蓬勃发展提供坚实的技术支撑。e
相关推荐
- B站收藏视频失效?mybili 收藏夹备份神器完整部署指南
-
本内容来源于@什么值得买APP,观点仅代表作者本人|作者:羊刀仙很多B站用户都有过类似经历:自己精心收藏的视频突然“消失”,点开一看不是“已被删除”,就是“因UP主设置不可见”。而B站并不会主动通知...
- 中间件推荐初始化配置
-
Redis推荐初始化配置bind0.0.0.0protected-modeyesport6379tcp-backlog511timeout300tcp-keepalive300...
- Redis中缓存穿透问题与解决方法
-
缓存穿透问题概述在Redis作为缓存使用时,缓存穿透是常见问题。正常查询流程是先从Redis缓存获取数据,若有则直接使用;若没有则去数据库查询,查到后存入缓存。但当请求的数据在缓存和数据库中都...
- 后端开发必看!Redis 哨兵机制如何保障系统高可用?
-
你是否曾在项目中遇到过Redis主服务器突然宕机,导致整个业务系统出现数据读取异常、响应延迟甚至服务中断的情况?面对这样的突发状况,作为互联网大厂的后端开发人员,如何快速恢复服务、保障系统的高可用...
- Redis合集-大Key处理建议
-
以下是Redis大Key问题的全流程解决方案,涵盖检测、处理、优化及预防策略,结合代码示例和最佳实践:一、大Key的定义与风险1.大Key判定标准数据类型大Key阈值风险场景S...
- 深入解析跳跃表:Redis里的"老六"数据结构,专治各种不服
-
大家好,我是你们的码农段子手,今天要给大家讲一个Redis世界里最会"跳科目三"的数据结构——跳跃表(SkipList)。这货表面上是个青铜,实际上是个王者,连红黑树见了都要喊声大哥。...
- Redis 中 AOF 持久化技术原理全解析,看完你就懂了!
-
你在使用Redis的过程中,有没有担心过数据丢失的问题?尤其是在服务器突然宕机、意外断电等情况发生时,那些还没来得及持久化的数据,是不是让你夜不能寐?别担心,Redis的AOF持久化技术就是...
- Redis合集-必备的几款运维工具
-
Redis在应用Redis时,经常会面临的运维工作,包括Redis的运行状态监控,数据迁移,主从集群、切片集群的部署和运维。接下来,从这三个方面,介绍一些工具。先来学习下监控Redis实时...
- 别再纠结线程池大小 + 线程数量了,没有固定公式的!
-
我们在百度上能很轻易地搜索到以下线程池设置大小的理论:在一台服务器上我们按照以下设置CPU密集型的程序-核心数+1I/O密集型的程序-核心数*2你不会真的按照这个理论来设置线程池的...
- 网络编程—IO多路复用详解
-
假如你想了解IO多路复用,那本文或许可以帮助你本文的最大目的就是想要把select、epoll在执行过程中干了什么叙述出来,所以具体的代码不会涉及,毕竟不同语言的接口有所区别。基础知识IO多路复用涉及...
- 5分钟学会C/C++多线程编程进程和线程
-
前言对线程有基本的理解简单的C++面向过程编程能力创造单个简单的线程。创造单个带参数的线程。如何等待线程结束。创造多个线程,并使用互斥量来防止资源抢占。会使用之后,直接跳到“汇总”,复制模板来用就行...
- 尽情阅读,技术进阶,详解mmap的原理
-
1.一句话概括mmapmmap的作用,在应用这一层,是让你把文件的某一段,当作内存一样来访问。将文件映射到物理内存,将进程虚拟空间映射到那块内存。这样,进程不仅能像访问内存一样读写文件,多个进程...
- C++11多线程知识点总结
-
一、多线程的基本概念1、进程与线程的区别和联系进程:进程是一个动态的过程,是一个活动的实体。简单来说,一个应用程序的运行就可以被看做是一个进程;线程:是运行中的实际的任务执行者。可以说,进程中包含了多...
- 微服务高可用的2个关键技巧,你一定用得上
-
概述上一篇文章讲了一个朋友公司使用SpringCloud架构遇到问题的一个真实案例,虽然不是什么大的技术问题,但如果对一些东西理解的不深刻,还真会犯一些错误。这篇文章我们来聊聊在微服务架构中,到底如...
- Java线程间如何共享与传递数据
-
1、背景在日常SpringBoot应用或者Java应用开发中,使用多线程编程有很多好处,比如可以同时处理多个任务,提高程序的并发性;可以充分利用计算机的多核处理器,使得程序能够更好地利用计算机的资源,...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- oracle位图索引 (63)
- oracle批量插入数据 (62)
- oracle事务隔离级别 (53)
- oracle 空为0 (50)
- oracle主从同步 (55)
- oracle 乐观锁 (51)
- redis 命令 (78)
- php redis (88)
- redis 存储 (66)
- redis 锁 (69)
- 启动 redis (66)
- redis 时间 (56)
- redis 删除 (67)
- redis内存 (57)
- redis并发 (52)
- redis 主从 (69)
- redis 订阅 (51)
- redis 登录 (54)
- redis 面试 (58)
- 阿里 redis (59)
- redis 搭建 (53)
- redis的缓存 (55)
- lua redis (58)
- redis 连接池 (61)
- redis 限流 (51)