文章目录
项目场景
需求:
Java中实现异步的方式可以在许多场景下提高性能和并发处理能力。以下是一些适合使用异步编程的常见场景:
- 网络通信:当应用程序需要与外部系统进行通信时,例如通过HTTP请求或数据库查询,使用异步操作可以避免阻塞主线程,提高响应速度。
- 并行处理:对于需要并行处理多个任务的情况,例如批处理任务或数据处理任务,可以使用异步编程来同时处理多个任务,从而加速整体处理速度。
- 事件驱动:在需要处理大量事件的应用程序中,例如GUI应用程序或服务器端应用程序,使用异步编程可以使系统能够更有效地响应事件,提高用户体验或系统吞吐量。
- 定时任务:对于需要定期执行的任务,例如定时清理任务或定时数据备份任务,可以使用异步编程来执行这些任务,从而避免阻塞主线程并提高系统的可用性。
- IO操作:在进行文件读写或其他IO操作时,使用异步编程可以提高系统的吞吐量,因为IO操作通常是耗时的,通过异步方式可以同时处理多个IO操作。
问题描述
后端需求
- 在调用某个方法的时候采用异步的方式执行(执行线程),直接返回响应给前端,而不需要等待方法处理完后才能收到响应结果。
解决方案
可以创建一个线程池来管理异步任务的执行。当接收到请求时,可以将任务提交到线程池中执行,然后立即返回响应给前端。
1. 创建线程池
/**
* @BelongsProject: stu-evaluate-system
* @BelongsPackage: com.cx.evaluate.config
* @Author: LLong
* @CreateTime: 2023-09-26 14:59
* @Description: 自定义线程池bean配置
* @Version: 1.0
*/
@Configuration
@EnableAsync
public class ThreadPoolTaskConfig {
private static final int corePoolSize = 8; // 核心线程数(默认线程数)
private static final int maxPoolSize = 16; // 最大线程数
private static final int keepAliveTime = 10; // 允许线程空闲时间(单位:默认为秒)
private static final int queueCapacity = 100; // 缓冲队列数
private static final String threadNamePrefix = "taskExecutor-Service-"; // 线程池名前缀
@Bean("taskExecutor") // bean的名称,默认为首字母小写的方法名
public ThreadPoolTaskExecutor getAsyncExecutor(){
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(corePoolSize);
executor.setMaxPoolSize(maxPoolSize);
executor.setQueueCapacity(queueCapacity);
executor.setKeepAliveSeconds(keepAliveTime);
ThreadPoolExecutor.CallerRunsPolicy refusalStrategy = new ThreadPoolExecutor.CallerRunsPolicy(); //拒绝策略
executor.setRejectedExecutionHandler(refusalStrategy);
executor.setThreadNamePrefix(threadNamePrefix);
// 线程池对拒绝任务的处理策略(提交该任务的线程去处理该任务)
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 初始化
executor.initialize();
return executor;
}
}
2. 调用异步方法(两种情况)
1. 异步方法和调用它的方法都在同一个 Service 类中
如果异步方法和调用它的方法都在同一个 Service 类中,你可以直接调用该方法,而不需要使用代理。在这种情况下,异步方法的调用将在同一个线程中执行,并不会异步执行。这意味着异步方法将会在调用它的方法中直接执行,而不会启动一个新的线程来执行异步任务。
这是因为Spring的异步支持通常通过代理机制实现,当你调用一个被 @Async 注解修饰的方法时,实际上是通过代理对象来调用方法的,从而实现异步执行。但如果调用方法和异步方法在同一个类中,Spring并不会创建代理对象,而是直接调用方法,导致方法在同一个线程中执行。
所以,在同一个类中的方法调用不会触发异步执行,如果你想要在同一个类中异步执行任务,你需要确保异步方法被调用的方式是在一个独立的线程上执行,比如通过一个新的线程或者另一个对象的方法调用。
@Service
public class EvaluateLogPushServiceImpl {
@Qualifier("taskExecutor")
@Autowired
ThreadPoolTaskExecutor threadPoolTaskExecutor;
public void importEvaluateLogByFront(List<EvaluateLogImportExcelVo> vos){
//将成功导入的评价记录进行同步 (异步执行)
importSynAsync(String.valueOf(fid), importEvaluateLogBySuccessList, weekEntities);
}
/**
* 异步执行同步方法(与调用它的方法都在同一个 Service 类)
*/
public void importSynAsync(String fid, List<EvaluateLogEntity> evaluateLogList, List<WeekEntity> weekEntities) {
threadPoolTaskExecutor.execute(() -> {
try {
//调用其他需要执行的方法
String message = String.format("成功导入%s条记录,同步推送成功%s条,同步推送失败%s条", evaluateLogList.size(), successCount, failCount);
System.out.println(message);
} catch(Exception e){
System.out.println("同步推送异常,e:"+e.getMessage());
}
});
}
}
2. 异步方法和调用它的方法不在同一个 Service 类中
@Service
public class OtherServiceImpl {
/**
* 异步执行同步方法(与调用它的方法不在同一个 Service 类)
*/
@Async("taskExecutor")// 指定使用自定义的线程池
public void importSynAsync(String fid, List<EvaluateLogEntity> evaluateLogList, List<WeekEntity> weekEntities) {
//调用其他需要执行的方法
String message = String.format("成功导入%s条记录,同步推送成功%s条,同步推送失败%s条", evaluateLogList.size(), successCount, failCount);
System.out.println(message);
}
}
3. execute()与submit() 的区别
execute() 和 submit() 是 ExecutorService 接口中定义的两个方法,都用于提交任务给线程池执行,但它们有一些区别:
- 返回类型:
- execute() 方法没有返回值,因此无法获取任务执行的结果。
- submit() 方法返回一个 Future 对象,可以用来获取任务执行的结果或者取消任务。
- 异常处理:
- execute() 方法不会抛出任务执行过程中的异常,异常会被捕获并传递给 Thread.UncaughtExceptionHandler 进行处理。
- submit() 方法可以通过 Future 对象获取任务执行过程中的异常,可以通过调用 get() 方法来获取结果,如果任务执行抛出异常,则在调用 get() 方法时会将异常重新抛出。
- 任务类型:
- execute() 方法只接受 Runnable 类型的任务,无法处理带有返回值的任务。
- submit() 方法既可以接受 Runnable 类型的任务,也可以接受 Callable 类型的任务,因此可以处理带有返回值的任务。
- 方法调用:
- execute() 方法是 Executor 接口中定义的方法。
- submit() 方法是 ExecutorService 接口中定义的方法,是 Executor 的子接口。
一般来说,如果你需要获取任务执行的结果或者处理任务执行过程中的异常,推荐使用 submit() 方法;如果不需要关心任务执行的结果,只需要提交任务给线程池执行,那么可以使用 execute() 方法。