@Async

工作中已经能使用到异步执行的情况,比如:发送邮件、日志记录、发送消息等等。判断一个功能是否应该异步执行的方法是这个流程是不是主流程必须的,是否需要用户交互才能进行下去。

比如发送邮件这个功能,一般是主流程中的状态发生改变时或者触发了某个特定条件,需要通知相关人员周知。在用户页面的操作完成之后,服务器端处理相关请求之后,得到页面应该显示的结果之后应该立刻返回给前端。而不是等待发送邮件完成之后,才把前端所需的结果返回展示给用户。这样做有两个好处:

  • 1、不等发送邮件完成之后就立刻返回结果,从用户角度来看,页面反应速度会比较快,更加用户友好。
  • 2、如果发送邮件嵌在了主流程,那么发送邮件发生代码执行错误之后,会阻塞主流程。比如发送邮件时连不上邮件服务器,导致一直重试,前端页面就会长时间没有响应。 发送邮件只是辅助流程,通知用户相关信息,我们应该能够忍受没有接收到邮件,但是不能接收前端页面报错或者无法完成交互工作。

一般遇到上面的情况之后,解决方法就是另开线程执行这些执行时间比较长、或者有阻塞主流程风险并且不需要跟主流程串行的功能代码。开线程执行,比较常用的方法可能就是:

1
2
3
4
5
6
new Thread(new Runnable(){
@Override
public void run() {
// 执行代码 发送邮件、发送消息、日志记录等操作
}
}).start();

上面的代码应该是我们最简单最常见的方式了。但是这种方式每次都需要重新申请资源、开启新的线程,执行完成之后线程需要销毁、回收资源。如果并发比较大的时候可能会造成资源浪费和服务器CPU压力。所以我们一般都使用线程池进行多线程执行。

1
2
3
4
5
<bean id="targetExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<property name="corePoolSize" value="10" />
<property name="waitForTasksToCompleteOnShutdown" value="true" />
<property name="awaitTerminationSeconds" value="60" />
</bean>

配合Spring依赖注入功能,我们可以全局配置一个线程池用来执行需要多线程执行的方法或者代码块。

1
2
3
4
5
6
7
8
9
10
targetExecutor.execute(new Runnable() {
@Override
public void run() {
try {
// 执行代码
} catch (BaseException e) {
logger.error("error.", e);
}
}
});

除了上面的直接使用execute方法外,我们还可以只用更简单的方式:@Async

使用@Async需要几个必要条件:

  • 1、这个注解需要加在使用Spring进行统一Bean管理的类的方法上才会生效。
  • 2、异步方法必须是public作用范围。
  • 3、异步方法返回值必须是void或者Future
  • 4、异步方法的调用必须是一个类调用另一个类,内部调用不起作用(动态代理的原理)

在这里留个坑吧。@Async@Transactional同时使用在一个方法上的问题。