任务分批处理-多线程实践

453 阅读1分钟

背景

在日常开发中,我们会遇到某些接口比较耗时,分析下来,是由于代码中要处理一个数据量很大的集合,这个时候我们可以将这个集合拆成多个子集合,利用多线程来处理。

1、使用场景

  1. 一个集合有1000个对象,分10个线程同时处理
  2. 从数据库中查1000条数据,分10个线程同时处理(这种场景要注意数据库的连接数,线程数不能太大,否则会把数据库堵死,如果SQL很复杂,这种场景建议使用MQ做)

2、代码示例

2.1、多线程处理类 BatchTask

@Slf4j
public abstract class BatchTask implements Runnable {
    private ThreadPoolExecutor pool;

    //分割后的list
    private List<SplitListUtil.SubRange> rangeList;
    
    //由子类去实现,处理该范围内的数据要干的业务
    protected abstract void executeByRange(int start, int end);

    /**
     * 由子类去实现,处理该范围内的数据要干的业务
     * total:总数
     * perSize:每份多少条
     */
    public BatchTask(int total, int perSize) {
        initDefaultPool();
        rangeList = SplitListUtil.averageAssign(total, perSize);
    }

    @Override
    public void run() {
        long st = DateUtils.nowMill();
        CountDownLatch latch = new CountDownLatch(rangeList.size());

        for (SplitListUtil.SubRange range : rangeList) {
            CompletableFuture.runAsync(() -> {
                try {
                    executeByRange(range.getStart(), range.getEnd());

                    log.info("BATCH TASK >> execute range {} ~ {} finish", range.getStart(), range.getEnd());
                } catch (Exception e) {
                    log.error("BATCH TASK >> execute range {} ~ {} error [{}]", range.getStart(), range.getEnd(), e.getMessage(), e);
                } finally {
                    latch.countDown();
                }
            }, pool);
        }

        try {
            latch.await();
        } catch (InterruptedException ignored) {
        }

        log.info("BATCH TASK >> finish execute all task cost time: [{}]ms", DateUtils.nowMill() - st);
    }

    /**
     * 初始化默认线程池
     */
    private void initDefaultPool() {
        if (pool != null) return;

        final AtomicInteger index = new AtomicInteger(0);

        pool = new ThreadPoolExecutorWrapper(10,
                10,
                60, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(100),
                r -> {
                    Thread t = new Thread(r);
                    t.setName("batch-task-pool-" + index.incrementAndGet());
                    return t;
                },
                new ThreadPoolExecutorWrapper.CallerRunsPolicy());

        pool.prestartAllCoreThreads();
    }

2.2、工具类 SplitListUtil

public class SplitListUtil {

    /**
     * 均分list
     *
     * @param total 列表长度
     * @param n     每份多少个
     */
    public static List<SubRange> averageAssign(int total, int n) {
        if (total <= 0 || n <= 0) return new ArrayList<>();

        List<SubRange> result = new ArrayList<>();
        int size = ((total) / n) + 1;
        for (int i = 0; i < size; i++) {
            SubRange range = new SubRange();
            range.setNum(i + 1);
            range.setStart(i * n);
            range.setEnd(Math.min((i + 1) * n, total));

            result.add(range);
        }
        return result;
    }

    @Data
    public static class SubRange {
        private int num;
        private int start;
        private int end;
    }

}

2.3、业务处理类 DemoTask

@Slf4j
public class DemoTask extends BatchTask {

    private final AtomicInteger count = new AtomicInteger(0);

    public DemoTask(int total,DemoDaoImpl demoDaoImpl) {
        super(total, 100);
        this.demoDaoImpl = demoDaoImpl;
    }

    @Override
    protected void executeByRange(int start, int end) {
        List<DemoDO> demoDOS = demoDaoImpl.queryPage(skip,limit);
        //TODO 具体的业务
      
        log.info("===end===第"+count.incrementAndGet()+"个===end===");
        });
    }
}

2.3、调用类 DemoController

@RestController
@RequestMapping("/batch")
public class DemoController {

    @Autowired
    DemoDaoImpl demoDaoImpl;
    
    @GetMapping(value = "/useThread")
    public void batchUseThread(@RequestParam Integer total) {
        new SendQualictToUserCenterTask(demoDaoImpl, total);
    }
}