目录

  • 一. 前言
  • 二. 示例

一. 前言

  1. 承接上文(不重要),获取到的数据需要进行分析,而数据量过大时,只依赖主线程会导致当前请求长时间锁定以至于超时,而且单个线程的处理效率太低,耗时超长。此时就需要用到多线程的操作异步处理数据。
  2. 分析场景:
  1. 多线程操作有两种:IO密集型和CPU密集型,不同的多线程操作做不同的线程数量配置,我这里的场景因为要大量读取数据库,故为IO密集型;
  • IO密集型:CPU逻辑核心数*2 或者 CPU逻辑核心数/(1-0.9)
  • CPU密集型:CPU逻辑核心数 + 1 或者 CPU逻辑核心数*2
  • 最佳配置:((线程IO等待时间 + 线程CPU时间) / 线程CPU时间) * CPU逻辑核心数
  1. 问题:
  1. 虽然创建了最大线程数量的任务类,但是同时执行任务的线程数量只有设定的核心线程的数量,这是因为LinkedBlockingDeque无界的任务队列的特性,线程池的任务队列可以无限制的添加新的任务,而线程池创建的最大线程数量就是corePoolSize设置的数量,当线程池的线程数达到corePoolSize后,就不会再增加了。
  2. 因为每个任务执行的时间并不稳定,如果平均分配任务,有可能执行快的线程提前完成任务后开始空闲。此时如果使用while()加小粒度分配任务数量的方式来多线程执行,执行完毕任务的线程会到队列中检查是否还有没有执行的任务,这样就更高效了。

二. 示例

  1. application.yml配置(非必须,只是后面改起来方便)
mine:
  thread:
    test:
      core-pool-size: 8
      max-pool-size: 16
  1. 线程池配置映射类(搭配1用的,不用的话可以不配)
@Data
@Component
@ConfigurationProperties(prefix = "mine.thread.test")
public class TestPoolCnfProperties {
    private Integer corePoolSize;
    private Integer maxPoolSize;
}
  1. 业务类
@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);
        }
    }
}
  1. 线程执行类
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());
        }
    }
}