背景
在日常开发中,我们会遇到某些接口比较耗时,分析下来,是由于代码中要处理一个数据量很大的集合,这个时候我们可以将这个集合拆成多个子集合,利用多线程来处理。
1、使用场景
- 一个集合有1000个对象,分10个线程同时处理
- 从数据库中查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);
}
}