Spring应用-4-SpringBoot多线程处理数据
一. 前言
- 承接上文(不重要),获取到的数据需要进行分析,而数据量过大时,只依赖主线程会导致当前请求长时间锁定以至于超时,而且单个线程的处理效率太低,耗时超长。此时就需要用到多线程的操作异步处理数据。
- 分析场景:
- 多线程操作有两种:IO密集型和CPU密集型,不同的多线程操作做不同的线程数量配置,我这里的场景因为要大量读取数据库,故为IO密集型;
- IO密集型:CPU逻辑核心数*2 或者 CPU逻辑核心数/(1-0.9)
- CPU密集型:CPU逻辑核心数 + 1 或者 CPU逻辑核心数*2
- 最佳配置:((线程IO等待时间 + 线程CPU时间) / 线程CPU时间) * CPU逻辑核心数
- 多线程操作有两种:IO密集型和CPU密集型,不同的多线程操作做不同的线程数量配置,我这里的场景因为要大量读取数据库,故为IO密集型;
- 问题:
- 虽然创建了最大线程数量的任务类,但是同时执行任务的线程数量只有设定的核心线程的数量,这是因为LinkedBlockingDeque无界的任务队列的特性,线程池的任务队列可以无限制的添加新的任务,而线程池创建的最大线程数量就是corePoolSize设置的数量,当线程池的线程数达到corePoolSize后,就不会再增加了。
- 因为每个任务执行的时间并不稳定,如果平均分配任务,有可能执行快的线程提前完成任务后开始空闲。此时如果使用while()加小粒度分配任务数量的方式来多线程执行,执行完毕任务的线程会到队列中检查是否还有没有执行的任务,这样就更高效了。
- 扩展阅读
二. 示例
- application.yml配置(非必须,只是后面改起来方便)
mine: thread: test: core-pool-size: 8 max-pool-size: 16
- 线程池配置映射类(搭配1用的,不用的话可以不配)
@Data @Component @ConfigurationProperties(prefix = "mine.thread.test") public class TestPoolCnfProperties { private Integer corePoolSize; private Integer maxPoolSize; }
- 业务类
@Service public class TestService { @Resource private TestPoolCnfProperties poolCnf; @Resource private TestMapper mapper; public void runThread() { // 准备数据 List<String> idList = mapper.getIds(); // 创建线程池 ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(poolCnf.getCorePoolSize(), poolCnf.getMaxPoolSize(), 60L, TimeUnit.SECONDS, new LinkedBlockingDeque<>()); // 均分方式分配任务:对于每个任务执行时间差不多情况下,效率比较高 int avg = idList.size() / poolCnf.getMaxPoolSize(); avg = avg != 0 ? avg : 1; // 计算需要创建多少线程,如果任务数量不够线程数量,不需要全创建 int maxThreadSize = idList.size() >= poolCnf.getMaxPoolSize() ? poolCnf.getMaxPoolSize() : idList.size(); // 循环分配任务并启动子线程 for (int i = 0; i < maxThreadSize; i++) { TestRunnable testRunnable; if (i + 1 < maxThreadSize) { // 这里把this传入是因为所有的数据库操作和数据处理操作逻辑都放在当前类中 testRunnable = new TestRunnable(this, idList.subList(i * avg, (i + 1) * avg)); } else { // 由最后一个线程执行所有剩余的任务 testRunnable = new TestRunnable(this, idList.subList(i * avg, idList.size())); } // 启动 poolExecutor.submit(testRunnable); } } }
- 线程执行类
public class TestRunnable implement Runnable { private TestService service; private List<String> idList; public TestRunnable(TestService service, List<String> idList) { this.service = service; this.idList = idList; } @Override public void run() { // 注意,子线程无法向上抛出异常,需要内部处理,如果不处理,子线程会中断并处于等待状态 try { // service.disposeXXX(); } catch (Exception e) { System.out.println(e.toString()); } } }