SpringBoot中的异步编程—@Async

2,832 阅读3分钟

@Async 是什么

void test() {
	A();
	B();
	C();
}

在没有Async的情况下,上面的方法是顺序执行的,也可以称为同步调用. B要在A执行完毕之后执行,C需要在B执行完毕之后执行,整个函数结束是在C执行完毕之后。

但是如果给B添加了@Async,执行顺序不变, 在执行完A之后,调用B,但是并不等待B完成,就执行C,C执行完毕之后,这个函数就执行完毕了.

在Java中,一般在处理类似的场景之时,都是基于创建独立的线程去完成相应的异步调用逻辑,通过主线程和不同的线程之间的执行流程,从而在启动独立的线程之后,主线程继续执行而不会产生停滞等待的情况。

什么时候需要添加@Async

根据业务需求,可以将暂时不需要获得处理的方法设置为@Async.

比如用户在前端点击完成了登录操作,这时候根据业务要求需要在登录成功之后进行埋点的处理.

其实埋点成功与否都不影响用户操作,这时候就可以将埋点方法设置为@Async.

个人认为此类任务通常有三个特征:

  1. 业务优先级低.
  2. 运行时间长,可能会造成卡顿.
  3. 返回结果暂时不立即处理,包括exception.

如何使用@Async

  1. 对当前Application添加@EnableAsync,当然也可以添加到Config,主要的是让Spring知道你有开启Async.
@EnableAsync
@SpringBootApplication
public class TestApplication {

  public static void main(String[] args) {
    SpringApplication.run(TestApplication.class, args);
  }
}
  1. 对当前方法添加Async
@Async
public void B() {

}
  1. 注意事项 对Static方法修饰无效! 调用与被修饰方法不能写在同一个函数中。

比如:

class Test {
	
	public void A() {
		B(); // 这时候不会触发Async
	}

	@Async
	public void B() {

	}

}

所以需要拆开调用:

class Test {
	
	public void A() {
		TestB testB = new TestB();
		testB.B();
	}
}

class TestB {
	@Async
	public void B() {

	}
}
  1. 返回值只能为Future或者Void, Future的使用方式.
@Async
public Future<String> asyncMethod() {
  SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//设置日期格式
  System.out.println("StartTime - " + Thread.currentThread().getName() + df.format(new Date()));
  try {
    Thread.sleep(1000);
  } catch (Exception e) {
    e.printStackTrace();
  }
  System.out.println("EndTime - " + Thread.currentThread().getName() + df.format(new Date()));
  return new AsyncResult<String>("hello world!");
}

@Test
public void contextLoads() {
  Future<String> result1 = test.asyncMethod();
  Future<String> result2 = test.asyncMethod();
  Future<String> result3 = test.asyncMethod();
  while (!result1.isDone() || !result2.isDone() || !result3.isDone()) {
    try {
      Thread.sleep(1000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }
}

自定义线程池

@Configuration
public class TaskConfiguration {
  @Bean("taskExecutor")
  public Executor taskExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(10); // 线程池创建时候初始化的线程数
    executor.setMaxPoolSize(20); // 线程池最大的线程数,只有在缓冲队列满了之后,才会申请超过核心线程数的线程
    executor.setQueueCapacity(200); // 缓冲任务队列的大小
    executor.setKeepAliveSeconds(60); // 允许线程的空闲时间,超过会被销毁
    executor.setThreadNamePrefix("custom-prefix-");// 线程的前缀
    executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); // 对拒绝任务的处理策略

    return executor;
  }
}

//定义之后在Async中指定
@Async("taskExecutor")
......

由于在应用关闭的时候异步任务还在执行,导致类似 数据库连接池 这样的对象一并被 销毁了,当 异步任务 中对 数据库 进行操作就会出错。

setWaitForTasksToCompleteOnShutdown(true): 该方法用来设置 线程池关闭 的时候 等待 所有任务都完成后,再继续 销毁 其他的 Bean,这样这些 异步任务销毁 就会先于 数据库连接池对象 的销毁。 setAwaitTerminationSeconds(60): 该方法用来设置线程池中 任务的等待时间,如果超过这个时间还没有销毁就 强制销毁,以确保应用最后能够被关闭,而不是阻塞住。