多线程下的分页优化记录

566 阅读1分钟

一、问题出现的背景

现在需要多线程去跑批数据,所以采用了多线程和分页的结合,但是遇到一个问题。

比如现在开了10个线程在跑数据(前提是这些处理数据的先后顺序没有要求),页容是10,我们采用countDownLatch来控制分页数据不会将线程池的队列塞满,我们会在每次分页,将线程休眠。话不多少,直接上代码:

这里线程池就用默认的了,实际开发中一定要自己指定参数。

@Service
@RequiredArgsConstructor
public class CountDownLatchService {

    private final EntityMapper entityMapper;

    public void begin() {
        //线程池
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        //CountDownLatch 计数:10
        //当前页数
        int nowPage = 1;
        //页容
        int pageSize = 10;
        //循环标记
        boolean flag = true;
        while (flag) {
            //分页查询数据
            Page<Entity> entityPage = entityMapper.pageByCode(new Page<>(nowPage, pageSize));
            //计数规则为当前分页还有多少数据
            CountDownLatch countDownLatch = new CountDownLatch((int) entityPage.getSize());

            List<Entity> records = entityPage.getRecords();
            //循环处理
            records.forEach(entity -> executorService.execute(() -> {
                try {
                    deal(entity);
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    //防止任务出错,次数无法扣减
                    countDownLatch.countDown();
                }
            }));
            try {
                countDownLatch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                //页数+1
                nowPage++;
            }
            //判断此处是否需要跳出循环
            if (entityPage.getSize() < pageSize) {
                //代表当前正在处理的这一页,数量已少于规定的页容
                flag = false;
            }
        }
        executorService.shutdown();
    }

    private void deal(Entity entity) {
        //do something
    }

}

这边有这样几个问题,比如10个线程 其中有9个处理完了,剩下的一个线程还在执行,比较慢,这时候就会有9个线程等1的情况。造成了资源的浪费。

二、优化措施

我们有这样几个思路

1.采用callable,使线程的执行结果被我们感知

2.采用生产者消费者队列,我们使用一个容量比线程数稍微大一点的阻塞队列,生产者通过分页往队列中写数据,满时,新的任务暂时阻塞住,线程去消费我们的队列的数据,这样可以实现效果。

3.Semaphore 我们通过颁发许可证的形式,控制线程

三、Semaphore 具体实现:

后来的优化我也是采用了这种方式,直接上代码


@Service
@RequiredArgsConstructor
public class ThreadTestServiceImpl implements ThreadTestService {

    private final EntityMapper entityMapper;

    @Override
    public void test() {
        //页容
        int pageSize = 10;
        //信号量 & 线程数
        int semaphoreNum = 10;
        Semaphore semaphore = new Semaphore(semaphoreNum);
        //线程池初始化
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        //正在执行中的
        //当前正在处理多少页
        int nowPageNum = 1;
        while (true) {
            Page<Entity> entityPage = entityMapper.pageByCode(new Page<>(nowPageNum, pageSize));
            executorService.execute(new Task(entityPage.getRecords(), semaphore));
            nowPageNum++;
            if(entityPage.getRecords().size() < pageSize){
                break;
            }
        }
        executorService.shutdown();
    }

    @AllArgsConstructor
    class Task implements Runnable {

        private List<Entity> list;
        private Semaphore semaphore;

        @Override
        public void run() {
            try {
                semaphore.acquire();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            deal(this.list);
            System.err.println(list);
            semaphore.release();
        }

    }

    public void deal(List<Entity> entityList) {
        //do something
    }

}

几个注意点

1.线程处理的不再是单条数据,处理单条数据不太好控制何时进行下次分页

2.具体的处理方法的报错情况一定要考虑到,否则容易卡住