百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

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里的&quot;老六&quot;数据结构,专治各种不服

大家好,我是你们的码农段子手,今天要给大家讲一个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应用开发中,使用多线程编程有很多好处,比如可以同时处理多个任务,提高程序的并发性;可以充分利用计算机的多核处理器,使得程序能够更好地利用计算机的资源,...

取消回复欢迎 发表评论: