单线程方式

批量插入数据的简单案例
如下:写了一个简单案例,创建了100w条数据,然后调用mybatis-plus批量新增方法

import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.date.StopWatch;
@Slf4j
@RestController
public class TestThreadController {

    @Resource
    private EventInfoService eventInfoService;

    @GetMapping("/add")
    public String add(){
        StopWatch stopWatch = new StopWatch();
        log.info("开始....");
        List<TEventInfo> list = new ArrayList<>();
        for (int i = 0; i < 1000000; i++) {
            list.add(new TEventInfo(i+"","1","消息内容"+i));
        }
        stopWatch.start();
        eventInfoService.saveBatch(list);
        stopWatch.stop();
        log.info("单线程耗时:{}ms",stopWatch.getLastTaskTimeMillis());
        return "success";
    }
}

异步线程池方式

同样是创建了100w条数据
然后进行分割每10000条一组,然后交给异步方法进行调用
主线程创建了CountDownLatch
使用latch.await()进行线程阻塞,这样方便查看所有线程执行完成的耗时

import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.date.StopWatch;
@Slf4j
@RestController
public class TestThreadController {

    @Resource
    private EventInfoService eventInfoService;
    
    @GetMapping("/add2")
    public String add2(){
        StopWatch stopWatch = new StopWatch();
        log.info("开始....");
        List<TEventInfo> list = new ArrayList<>();
        for (int i = 0; i < 1000000; i++) {
            list.add(new TEventInfo(i+"","1","消息内容"+i));
        }
        List<List<TEventInfo>> lists = ListUtil.partition(list, 10000);
        CountDownLatch latch = new CountDownLatch(lists.size());
        // 计时开始
        stopWatch.start();
        for (List<TEventInfo> infoList : lists) {
            eventInfoService.saveAll(infoList,latch);
        }
        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 计时结束
        stopWatch.stop();
        log.info("线程池耗时:{}ms,{}ns",stopWatch.getLastTaskTimeMillis() ,stopWatch.getLastTaskTimeNanos());
        return "success";
    }
}

使用注解标记方法使用异步@Async("asyncExecutor")

@Service
public class EventInfoServiceImpl extends ServiceImpl<EventInfoMapper, EventInfo> implements EventInfoService {
	
	// 实现类还是调用了批量新增的方法
	// 不过传入的CountDownLatch 方便检测整个线程池跑完要花费多少时间
    @Override
    @Async("asyncExecutor")
    @Transactional(rollbackFor = Exception.class)
    public void saveAll(List<EventInfo> infoList, CountDownLatch latch) {
        try {
            saveBatch(infoList);
        } finally {
            latch.countDown();
        }
    }
}

一般springBoot默认自带的@Async是不推荐使用的,但是我们可以自定义线程池

@Configuration
public class ThreadPoolConfig {

    @Bean
    public ThreadPoolTaskExecutor asyncExecutor(){
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // IO 密集型:cpu核心x2 + 1
        executor.setCorePoolSize(33);
        // IO 密集型:cpu核心x4
        executor.setMaxPoolSize(66);
        // 队列容量
        executor.setQueueCapacity(120);
        // 线程空闲时间
        executor.setKeepAliveSeconds(30);
        // 给每个线程池名称设置前缀
        executor.setThreadNamePrefix("zxh-");
        executor.initialize();
        return executor;
    }
}

启动类上需要加上这个注解@EnableAsync

@EnableAsync
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
       SpringApplication.run(IpSourceApplication.class, args);
    }
}

测试结果

多线程方式可以提高插入数据的效率,网络对数据新增的影响也很大。
关闭mybaties日志输出

// 单线程
INFO 28320 --- [nio-8080-exec-4] c.z.ip.controller.TestThreadController   : 开始....
INFO 28320 --- [nio-8080-exec-4] c.z.ip.controller.TestThreadController   : 单线程耗时:87568ms
// 线程池 核心线程数33 最大线程数66
INFO 28320 --- [nio-8080-exec-2] c.z.ip.controller.TestThreadController   : 开始....
INFO 28320 --- [nio-8080-exec-2] c.z.ip.controller.TestThreadController   : 线程池耗时:30268ms
// 线程池 核心线程数100 最大线程数200
INFO 29844 --- [nio-8080-exec-1] c.z.ip.controller.TestThreadController   : 开始....
INFO 29844 --- [nio-8080-exec-1] c.z.ip.controller.TestThreadController   : 线程池耗时:28153ms

结论

线程池方式对于提升速度有显著效果
对于IO密集型 更多的线程池对于提升速度效果不明显

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐