前言
最近对一个接口执行进行优化,先简单说说接口的详情以及优化的方向吧。本文所提供的解决方法并不是一个好方案,但是本文的目的是在解决问题的同时,加深对线程池的了解与使用。
1、接口工作内容:
该接口的主要工作内容是生成业务相关的图表(根据用户选择,可以生成1-n张图表),用户提交生成任务之后,便生成一个任务记录,初始状态为创建,用户将等待任务处理。任务状态有以下几种:创建、运行、失败、空数据、成功等,任务状态的变化将由任务执行情况而定。
2、接口现状:
在用户提交生成任务之后,会将生成图表的任务提交到线程池中去执行。但是一个生成任务中,生成图表的数量不定,可为1-n张,如果都由线程池中的一个线程去执行,那么将会十分耗时!我这里的业务场景测试下来,生成7000多张的图表,将耗时两小时不止。。。
3、优化方向:
由于一个线程去生成多张图表十分费时,于是做出以下改变:在线程池中的线程去执行多张图表生成任务时,首先是创建一个线程池,再将图表的生成任务提交至该线程池中,使得一个线程一次生成一张图表,如果在生成任意一张图表失败时,将视生成任务失败,则会关闭线程池,将图表生成任务标记为失败状态,否则,当所有图表都生成成功时,将图表生成任务标记为成功状态。
以上是这次多线程优化的相关内容,如果业务场景相同的话,可以耐心看看源代码,如果对你有帮助,那么我会很高兴的。
如果业务场景不同,但你又没有做过类似的业务,那么不妨静下心仔细看看,万一学到点东西了呢?是吧。
一、方案分析
这里不少人会觉得为什么需要在线程中再创建线程池,为什么不能直接将每张图表的生成给到外层线程池中,这样就不用再创建线程池了?
想法确实很美好,因为一个生成任务是需要生成n张图表的,而且任务的状态需要记录下来。如果把它们放到外层的线程池中去执行,那么,当有多个图表生成任务时,所有图表都混在一起生成,那么我怎么去记录不同图表生成任务的状态呢?这样我就不清楚哪个任务对应的所有图表生成成功,还是失败了。
但是,如果我在一个线程中创建一个线程池,那么这个线程中的线程池,他只会去执行某个图表生成任务的所有图表生成,而不会去执行到其他任务的,在空间上就进行了隔离。而且可以很方便的去检测到任务的执行情况。
这里会在每个线程执行时创建线程,所有图表生成结束后,也将会去关闭线程池。虽然频繁创建线程池和销毁线程池会有很大的开销,但是考虑到业务完成时间,以及实际接口的调用情况,边采用了当前这种方案。
二、解决方案
1、创建一个通用线程池,用于异步执行图表生成任务
@Configuration
public class ExecutorConfig {
public static final int availableProcessors = Runtime.getRuntime().availableProcessors();
public static final int queueCapacity = 100;
public static final int keepAliveTime = 60;
@Bean(name = "paintExecutor")
public Executor paintExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(availableProcessors * 2);
executor.setMaxPoolSize(availableProcessors * 4);
executor.setQueueCapacity(queueCapacity);
executor.setThreadNamePrefix("paint-service-");
executor.setKeepAliveSeconds(keepAliveTime);
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}
2、创建TaskVO,用于接收前端任务对象
@Data
public class TaskVO {
// 任务唯一标识
private String uuid;
// 需要生成图表的个数,这里模拟用户生成多张,实际需要生成的勾选相关数据进行生成
private int num;
// 其他参数就是你创建任务选择的一些相关参数,这里不写了,根据你的需求自己改造
}
3、创建一个Controller层接口,用于图表生成任务调用,调用该接口,新增任务记录,异步提交图表生成任务
@RestController
@Slf4j
public class ChartController {
@Autowired
private ChartService chartService;
@PostMapping("/createTask")
public R<Boolean> createTask(@RequestBody TaskVO taskVO) {
// 1、生成一个uuid,作为任务的唯一标识,后续根据uuid修改任务执行状态
String uuid = IDUtil.getUUID();
taskDTO.setUuid(uuid);
// 2、记录该任务,将uuid,初始状态、参数等相关信息进行记录,后续需要查找该记录
boolean add = chartService.addRecord(taskVO);
// 3、异步执行图表生成任务,该方法加有@Async("paintExecutor")注解
if(add){
chartService.submitTask(taskVO);
}
// 4、根据步骤1,返回任务创建结果
return new R<>(uuid, null, add);
}
}
提交图表生成任务使用异步的目的是为了,用户在创建任务成功后,将立即返回任务,图表生成的过程将会后台异步完成。
3、创建service
public interface ChartService{
/**
* 任务中心新增记录
* @param taskVO
* @return
*/
Boolean addRecord(TaskVO taskVO);
/**
* 1、异步执行图表生成任务
* 2、等待图表任务执行结果
* 3、修改任务状态以及其他处理
* @date 2022/7/5
*/
void submitTask(TaskVO taskVO);
}
4、创建实现类
@Service
@Slf4j
public class ChartServiceImpl implements ChartService{
/**
* 任务中心新增记录
* @param taskVO
* @return
*/
@Override
public Boolean addRecord(TaskVO taskVO){
// 内容略,自行向数据库插入一条数据,todo
}
/**
* 1、异步执行图表生成任务
* 2、修改任务状态以及其他处理
* @date 2022/7/5
*/
@Async("paintExecutor")
@Override
public void submitTask(TaskVO taskVO){
final int num = taskVO.getNum();
if(num <= 0){
return;
}
// 线程池创建
int core = Runtime.getRuntime().availableProcessors() * 2; // 图表生成主要多为io,设置为处理器核数两倍
int max = core; // 设置一样,避免线程抖动
// 设置任务拒绝策略为:DiscardPolicy,作用,遇到线程池关闭或者任务过长,直接把队列中的任务丢弃
final ExecutorService chartExecutorService = new ThreadPoolExecutor(core, max, 10,
TimeUnit.SECONDS, new LinkedBlockingQueue<>(10000));
// 计数器,用来计算任务执行结果
final AtomicInteger counter = new AtomicInteger(0);
// 锁,线程池关闭与向线程池提交任务同步,避免生成其中一张图出现异常进入线程池关闭期间与向线程池提交任务起冲突
final Object shutdownAndSubmitLock = new Object();
// 向图表线程池提交任务,生成num张图表
log.info("开始生成图表, uuid=" + taskVO.getUuid());
for(int i = 0 ;i < num; i++){
// 同步,避免关闭线程池与向线程池提交任务之间出现并发问题,导致线程池关闭之后还在向线程池提交任务,可以不加锁,使用线程池的拒绝策略。
// 任务提交由主线程加锁提交
synchronized (shutdownAndSubmitLock){
// 线程池没有被关闭,则提交任务
if(!chartExecutorService.isShutdown()){
chartExecutorService.execute(() ->{
try{
// 生成报表,todo
//执行成功,计数器+1
counter.incrementAndGet();
}catch (Exception e){
e.printStackTrace();
log.error("uuid=" + taskVO.getUuid() + ",图表生成过程中出现异常,原因为:" + e.getMessage());
// 更新任务为失败状态,todo
// 同步,避免关闭线程池与向线程池提交任务之间出现并发问题,导致线程池关闭之后还在向线程池提交任务,可以不加锁,使用线程池的拒绝策略
// 关闭线程池由出现执行异常的异步线程关闭
synchronized (shutdownAndSubmitLock){
if(!chartExecutorService.isShutdown()){
chartExecutorService.shutdownNow();
}
}
}finally {
// 所有图表生成完成
if(counter.get() == num){
log.info("图表生成结束, uuid=" + taskVO.getUuid());
// 更新任务为成功状态,todo
// 同步,避免关闭线程池与向线程池提交任务之间出现并发问题,导致线程池关闭之后还在向线程池提交任务,可以不加锁,使用线程池的拒绝策略
// 关闭线程池由执行最后一个线程关闭
synchronized (shutdownAndSubmitLock){
if(!chartExecutorService.isShutdown()){
chartExecutorService.shutdownNow();
}
}
}
}
});
}
}
}
}
}
线程池调用shutdownNow去关闭线程池后,后续提交或者在队列中等待的任务将不会执行到了。
总结
以上代码写的很复杂,确实不是一份好代码,但是以此作为记录,为下次问题的解决方案提供思路。如果有更好的方案,欢迎小伙伴分享给我。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/99677.html