一、问题出现的背景
现在需要多线程去跑批数据,所以采用了多线程和分页的结合,但是遇到一个问题。
比如现在开了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.具体的处理方法的报错情况一定要考虑到,否则容易卡住