Java实现异步的方式

项目场景

需求:

Java中实现异步的方式可以在许多场景下提高性能和并发处理能力。以下是一些适合使用异步编程的常见场景:

  1. 网络通信:当应用程序需要与外部系统进行通信时,例如通过HTTP请求或数据库查询,使用异步操作可以避免阻塞主线程,提高响应速度。
  2. 并行处理:对于需要并行处理多个任务的情况,例如批处理任务或数据处理任务,可以使用异步编程来同时处理多个任务,从而加速整体处理速度。
  3. 事件驱动:在需要处理大量事件的应用程序中,例如GUI应用程序或服务器端应用程序,使用异步编程可以使系统能够更有效地响应事件,提高用户体验或系统吞吐量。
  4. 定时任务:对于需要定期执行的任务,例如定时清理任务或定时数据备份任务,可以使用异步编程来执行这些任务,从而避免阻塞主线程并提高系统的可用性。
  5. 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 接口中定义的两个方法,都用于提交任务给线程池执行,但它们有一些区别:

  1. 返回类型:
  • execute() 方法没有返回值,因此无法获取任务执行的结果。
  • submit() 方法返回一个 Future 对象,可以用来获取任务执行的结果或者取消任务。
  1. 异常处理:
  • execute() 方法不会抛出任务执行过程中的异常,异常会被捕获并传递给 Thread.UncaughtExceptionHandler 进行处理。
  • submit() 方法可以通过 Future 对象获取任务执行过程中的异常,可以通过调用 get() 方法来获取结果,如果任务执行抛出异常,则在调用 get() 方法时会将异常重新抛出。
  1. 任务类型:
  • execute() 方法只接受 Runnable 类型的任务,无法处理带有返回值的任务。
  • submit() 方法既可以接受 Runnable 类型的任务,也可以接受 Callable 类型的任务,因此可以处理带有返回值的任务。
  1. 方法调用:
  • execute() 方法是 Executor 接口中定义的方法。
  • submit() 方法是 ExecutorService 接口中定义的方法,是 Executor 的子接口。

一般来说,如果你需要获取任务执行的结果或者处理任务执行过程中的异常,推荐使用 submit() 方法;如果不需要关心任务执行的结果,只需要提交任务给线程池执行,那么可以使用 execute() 方法。

  • 16
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值