Async注解使用及源码分析

在实际开发中,有时需要执行某个方法但不需等待该方法的执行结果或者需要执行多个方法但这些方法不需要先后执行。针对上述场景,可以通过声明并调用异步方法实现。SpringBoot提供@Async注解用于实现异步方法的声明和调用。接下来将介绍@Async注解的使用并从源码层面分析其实现。

@Async注解使用

@Async注解使用至少需要三步:(1)启用异步调用(启动配置添加@EnableAsync注解);(2)声明异步方法(使用@Async装饰方法);(3)调用异步方法。在商用环境中,因为每执行一个异步方法都需要从线程池中申请并占用一个线程,为避免线程资源过度损耗,需要自行维护线程池。

(1) 启用异步调用

@Async注解生效的前提是启用异步调用。可以在自定义的配置类(@Configuration装饰)中或启动类(main方法所在类)中添加@EnableAsync注解。这里在启动类中启用启用异步调用。示例代码如下:

@EnableAsync
@SpringBootApplication(scanBasePackages = "com.github.courage007.springbootasync")
public class SpringBootAsyncApplication {
    
    
    public static void main(String[] args) {
    
    
        SpringApplication.run(SpringBootAsyncApplication.class, args);
    }
}

(2)声明异步方法

启用异步调用后,接下来就可以声明并调用异步方法。声明异步方法的方法很简单,就是使用@Async装饰方法。唯一需要注意的就是@Async注解的生效场景,如同一个类中的不同方法调用无法触发异步调用、@Async注解装饰非公有方法时无效、@Async注解装饰静态方法时无效等(本质是Spring AOP生效的场景)。这里给出@Async注解的源码定义:

@Target({
    
    ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Async {
    
    
    String value() default "";
}

异步方法有两种类型,一种是无返回值,一种是有返回值,针对需要执行某个方法但不需等待该方法的执行结果或者需要执行多个方法但这些方法不需要先后执行两种场景。无返回值场景,和常规写法没什么不同,但是有返回值的话,需要将返回值包在 Future 对象中。示例代码如下:

@Service
@Slf4j
public class AsyncService {
    
    
    /**
     * 异步调用示例,无返回值
     */
    @Async
    public void asyncInVoid() {
    
    
        System.out.println("asyncInVoid begin");
        try {
    
    
            Thread.sleep(5000);
            System.out.println("current time is: " + System.currentTimeMillis());
        } catch (InterruptedException ex) {
    
    
            log.error("thread sleep failed: ", ex);
        }
        System.out.println("asyncInVoid end");
    }

    /**
     * 异步调用示例,有返回值
     *
     * @return
     */
    @Async
    public Future<Integer> asyncWithResult() {
    
    
        System.out.println("asyncWithResult begin");
        try {
    
    
            Thread.sleep(10000);
            System.out.println("current time is: " + System.currentTimeMillis());
        } catch (InterruptedException ex) {
    
    
            log.error("thread sleep failed: ", ex);
        }
        System.out.println("asyncWithResult end");
        return new AsyncResult<Integer>(1000);
    }
}

(3)调用异步方法

完成异步方法的声明后,接下来就可以使用异步方法。异步方法的调用和普通的方法调用没有特别不同,需要注意的是,调用无返回值的异步方法时,无需等待其执行结束;对于有返回值的异步方法时,获取其返回值时,需要同步等待。示例代码如下:

/**
 * [异步调用控制器]
 *
 * @author: courage007
 * @date: 2022/04/17 19:39
 */
@RestController
@RequestMapping("/hello")
public class AsyncController {
    
    
    @Autowired
    private AsyncService asyncService;

    @PostMapping("/async-task")
    public void doAsyncTask() {
    
    
        System.out.println("doAsyncTask begin");
        asyncService.asyncInVoid();
        System.out.println("doAsyncTask end");
    }

    @PostMapping("/async-task-with-result")
    public Integer doAsyncTaskWithResult() {
    
    
        System.out.println("doAsyncTaskWithResult begin");
        Future<Integer> result = asyncService.asyncWithResult();
        try{
    
    
            System.out.println("doAsyncTaskWithResult end");
            return result.get();
        } catch (ExecutionException | InterruptedException ex) {
    
    
            throw new RuntimeException("execute failed");
        }
    }

    @PostMapping("/async-task-with-result-2")
    public Integer doAsyncTaskWithResult2() {
    
    
        System.out.println("doAsyncTaskWithResult2 begin");
        Future<Integer> result = asyncService.asyncWithResult();
        try{
    
    
            System.out.println("doAsyncTaskWithResult end");
            Thread.sleep(15000);
            // get()方法会一直阻塞,方法最后的执行时间,依赖执行时间最长的线程
            return result.get();
        } catch (ExecutionException | InterruptedException ex) {
    
    
            throw new RuntimeException("execute failed");
        }
    }
}

(4)自定义异步方法调用线程池

异步方法运行在独立的线程中,不会阻塞主线程的执行。异步方法执行所需的线程默认是从SpringBoot提供线程池中申请线程。为控制线程的使用和回收,商用环境一般使用自定义线程池,以保证异步方法的调用可控。SpringBoot提供AsyncTaskExecutor用于实现自定义异步方法调用线程池。示例代码如下:

/**
 * [异步调用配置]
 *
 * @author: courage007
 * @date: 2022/04/18
 */
@Configuration
public class AsyncConfig {
    
    
    private static final int MAX_POOL_SIZE = 50;
    private static final int CORE_POOL_SIZE = 20;
    private static final int WAIT_QUEUE_CAPACITY = 1000;

    @Bean("taskExecutor")
    public AsyncTaskExecutor taskExecutor() {
    
    
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setThreadNamePrefix("async-task-thread-pool-");
        taskExecutor.setMaxPoolSize(MAX_POOL_SIZE);
        taskExecutor.setCorePoolSize(CORE_POOL_SIZE);
        taskExecutor.setQueueCapacity(WAIT_QUEUE_CAPACITY);
        taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        taskExecutor.initialize();
        return taskExecutor;
    }
}

更多线程的实践可以参考笔者之前的博文。经过上述步骤,就可以在商用环境实现异步编程。接下来将从源码层面分析@Async注解的实现。

@Async注解源码分析

介绍完Async注解的使用,接下来从源码层面深入分析其实现。

@Async注解

首先,简单分析下@Async注解源码。对应源码如下:

@Target({
    
    ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Async {
    
    
	/**
	 * A qualifier value for the specified asynchronous operation(s).
	 * <p>May be used to determine the target executor to be used when executing
	 * the asynchronous operation(s), matching the qualifier value (or the bean
	 * name) of a specific {@link java.util.concurrent.Executor Executor} or
	 * {@link org.springframework.core.task.TaskExecutor TaskExecutor}
	 * bean definition.
	 * <p>When specified on a class-level {@code @Async} annotation, indicates that the
	 * given executor should be used for all methods within the class. Method-level use
	 * of {@code Async#value} always overrides any value set at the class level.
	 * @since 3.1.2
	 */
	String value() default "";
}

查看其定义,该注解作用于方法或类,用来标记异步方法或异步类(该类的所有public方法都是异步方法)。同时,针对异步调用场景,包括两类:无返回值调用和有返回值调用。阅读注释,还可以知道,异步方法会从Executor或TaskExecutor中申请线程。这对我们后面使用自定义线程有参考意义。

@EnableAsync注解

为了实现异步调用的统一管理,Spring 框架引入@EnableAsync注解。@EnableAsync注解的源码如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AsyncConfigurationSelector.class)
public @interface EnableAsync {
    
    
	/**
	 * Indicate the 'async' annotation type to be detected at either class
	 * or method level.
	 * <p>By default, both Spring's @{@link Async} annotation and the EJB 3.1
	 * {@code @javax.ejb.Asynchronous} annotation will be detected.
	 * <p>This attribute exists so that developers can provide their own
	 * custom annotation type to indicate that a method (or all methods of
	 * a given class) should be invoked asynchronously.
	 */
	Class<? extends Annotation> annotation() default Annotation.class;

	/**
	 * Indicate whether subclass-based (CGLIB) proxies are to be created as opposed
	 * to standard Java interface-based proxies.
	 * <p><strong>Applicable only if the {@link #mode} is set to {@link AdviceMode#PROXY}</strong>.
	 * <p>The default is {@code false}.
	 * <p>Note that setting this attribute to {@code true} will affect <em>all</em>
	 * Spring-managed beans requiring proxying, not just those marked with {@code @Async}.
	 * For example, other beans marked with Spring's {@code @Transactional} annotation
	 * will be upgraded to subclass proxying at the same time. This approach has no
	 * negative impact in practice unless one is explicitly expecting one type of proxy
	 * vs. another &mdash; for example, in tests.
	 */
	boolean proxyTargetClass() default false;

	/**
	 * Indicate how async advice should be applied.
	 * <p><b>The default is {@link AdviceMode#PROXY}.</b>
	 * Please note that proxy mode allows for interception of calls through the proxy
	 * only. Local calls within the same class cannot get intercepted that way; an
	 * {@link Async} annotation on such a method within a local call will be ignored
	 * since Spring's interceptor does not even kick in for such a runtime scenario.
	 * For a more advanced mode of interception, consider switching this to
	 * {@link AdviceMode#ASPECTJ}.
	 */
	AdviceMode mode() default AdviceMode.PROXY;

	/**
	 * Indicate the order in which the {@link AsyncAnnotationBeanPostProcessor}
	 * should be applied.
	 * <p>The default is {@link Ordered#LOWEST_PRECEDENCE} in order to run
	 * after all other post-processors, so that it can add an advisor to
	 * existing proxies rather than double-proxy.
	 */
	int order() default Ordered.LOWEST_PRECEDENCE;
}

@EnableAsync注解作用在类上,除了表明启用异步调用功能外,还提供了一定的扩展能力,如执行异步方法时,使用自定义线程池。默认情况下,Spring会优先搜索TaskExecutor类型的bean或者名为taskExecutor的Executor类型的bean,如果都不存在的话,会使用SimpleAsyncTaskExecutor执行器(稍后会分析该线程池不能应用于商用环境的原因)。这就告诉我们,可以通过构造TaskExecutor类型的bean或者名为taskExecutor的Executor类型的bean来实现自定义的任务执行器(这也是上面的示例代码使用的方法)。

@SuppressWarnings("serial")
public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator
		implements AsyncListenableTaskExecutor, Serializable {
    
    
	/**
	 * Permit any number of concurrent invocations: that is, don't throttle concurrency.
	 * @see ConcurrencyThrottleSupport#UNBOUNDED_CONCURRENCY
	 */
	public static final int UNBOUNDED_CONCURRENCY = ConcurrencyThrottleSupport.UNBOUNDED_CONCURRENCY;

	/**
	 * Switch concurrency 'off': that is, don't allow any concurrent invocations.
	 * @see ConcurrencyThrottleSupport#NO_CONCURRENCY
	 */
	public static final int NO_CONCURRENCY = ConcurrencyThrottleSupport.NO_CONCURRENCY;

	/** Internal concurrency throttle used by this executor. */
	private final ConcurrencyThrottleAdapter concurrencyThrottle = new ConcurrencyThrottleAdapter();

	@Nullable
	private ThreadFactory threadFactory;

	@Nullable
	private TaskDecorator taskDecorator;


	/**
	 * Create a new SimpleAsyncTaskExecutor with the given thread name prefix.
	 * @param threadNamePrefix the prefix to use for the names of newly created threads
	 */
	public SimpleAsyncTaskExecutor(String threadNamePrefix) {
    
    
		super(threadNamePrefix);
	}

    /**
	 * Set the maximum number of parallel accesses allowed.
	 * -1 indicates no concurrency limit at all.
	 * <p>In principle, this limit can be changed at runtime,
	 * although it is generally designed as a config time setting.
	 * NOTE: Do not switch between -1 and any concrete limit at runtime,
	 * as this will lead to inconsistent concurrency counts: A limit
	 * of -1 effectively turns off concurrency counting completely.
	 * @see #UNBOUNDED_CONCURRENCY
	 */
	public void setConcurrencyLimit(int concurrencyLimit) {
    
    
		this.concurrencyThrottle.setConcurrencyLimit(concurrencyLimit);
	}

	/**
	 * Template method for the actual execution of a task.
	 * <p>The default implementation creates a new Thread and starts it.
	 * @param task the Runnable to execute
	 * @see #setThreadFactory
	 * @see #createThread
	 * @see java.lang.Thread#start()
	 */
	protected void doExecute(Runnable task) {
    
    
		Thread thread = (this.threadFactory != null ? this.threadFactory.newThread(task) : createThread(task));
		thread.start();
	}
    ...
}

分析SimpleAsyncTaskExecutor源码可知(从资源申请与占用角度),默认情况下,SimpleAsyncTaskExecutor不会限制线程创建的个数,这会导致资源耗尽。所以在商用环境,应注意SimpleAsyncTaskExecutor的使用。如果需要使用SimpleAsyncTaskExecutor,则需指定线程上限(调用setConcurrencyLimit方法),避免在极端情况下出现资源耗尽的问题。另外,该任务执行器并没有执行拒绝策略,这也是在商用环境需谨慎使用的原因之一。推荐使用自定义线程池,更多线程池的实践可以参考笔者之前的博客
此外,Spring还提供AsyncConfigurer用于帮忙开发者自定义任务执行器。AsyncConfigurer定义如下:

/**
 * Interface to be implemented by @{@link org.springframework.context.annotation.Configuration
 * Configuration} classes annotated with @{@link EnableAsync} that wish to customize the
 * {@link Executor} instance used when processing async method invocations or the
 * {@link AsyncUncaughtExceptionHandler} instance used to process exception thrown from
 * async method with {@code void} return type.
 */
public interface AsyncConfigurer {
    
    
	/**
	 * The {@link Executor} instance to be used when processing async
	 * method invocations.
	 */
	@Nullable
	default Executor getAsyncExecutor() {
    
    
		return null;
	}

	/**
	 * The {@link AsyncUncaughtExceptionHandler} instance to be used
	 * when an exception is thrown during an asynchronous method execution
	 * with {@code void} return type.
	 */
	@Nullable
	default AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
    
    
		return null;
	}
}

所以,除了通过构造TaskExecutor类型的bean或者名为taskExecutor的Executor类型的bean来实现自定义的任务执行器,还可以实现AsyncConfigurer,示例代码如下:

/**
 * [异步调用配置]
 *
 * @author: courage007
 * @date: 2022/04/18
 */
@Configuration
public class CustomAsyncExecutor implements AsyncConfigurer {
    
    
    private static final int MAX_POOL_SIZE = 50;

    private static final int CORE_POOL_SIZE = 20;

    private static final int WAIT_QUEUE_CAPACITY = 1000;
    
    @Override
    public Executor getAsyncExecutor() {
    
    
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setThreadNamePrefix("async-task-thread-pool-");
        taskExecutor.setMaxPoolSize(MAX_POOL_SIZE);
        taskExecutor.setCorePoolSize(CORE_POOL_SIZE);
        taskExecutor.setQueueCapacity(WAIT_QUEUE_CAPACITY);
        taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        taskExecutor.initialize();
        return taskExecutor;
    }
    
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
    
    
        return null;
    }
}

回到EnableAsync注解,除了获知异步调用选用任务执行器的规则外,还可知道EnableAsync支持用户自定义异步注解(默认扫描spring的@Async),也就是说,如果@Async不能满足需求,还可以自定义注解并在@EnableAsync中指定。对Spring来说,还支持基于优先级调用异步方法,这主要适应于需要指定异步方法调用的场景,默认最低优先级(Integer.MAX_VALUE,值越小优先级越高)也可指定为最高优先级。

public interface Ordered {
    
    
	/**
	 * Useful constant for the highest precedence value.
	 * @see java.lang.Integer#MIN_VALUE
	 */
	int HIGHEST_PRECEDENCE = Integer.MIN_VALUE;

	/**
	 * Useful constant for the lowest precedence value.
	 * @see java.lang.Integer#MAX_VALUE
	 */
	int LOWEST_PRECEDENCE = Integer.MAX_VALUE;
}

默认情况下,同一个类中的不同方法调用无法触发异步调用,这与异步通知的使用方式有关,也即PROXY模式,如需支持同一个类中非异步方法调用另一个异步方法,需要设置为ASPECTJ。

/**
 * Enumeration used to determine whether JDK proxy-based or
 * AspectJ weaving-based advice should be applied.
 */
public enum AdviceMode {
    
    
	/**
	 * JDK proxy-based advice.
	 */
	PROXY,
	/**
	 * AspectJ weaving-based advice.
	 */
	ASPECTJ
}

分析@EnableAsync注解源码,还可知实现异步调用实现的选择类。对应源码如下:

/**
 * Selects which implementation of {@link AbstractAsyncConfiguration} should
 * be used based on the value of {@link EnableAsync#mode} on the importing
 * {@code @Configuration} class.
 */
public class AsyncConfigurationSelector extends AdviceModeImportSelector<EnableAsync> {
    
    
	private static final String ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME =
			"org.springframework.scheduling.aspectj.AspectJAsyncConfiguration";
	/**
	 * Returns {@link ProxyAsyncConfiguration} or {@code AspectJAsyncConfiguration}
	 * for {@code PROXY} and {@code ASPECTJ} values of {@link EnableAsync#mode()},
	 * respectively.
	 */
	@Override
	@Nullable
	public String[] selectImports(AdviceMode adviceMode) {
    
    
		switch (adviceMode) {
    
    
			case PROXY:
				return new String[] {
    
    ProxyAsyncConfiguration.class.getName()};
			case ASPECTJ:
				return new String[] {
    
    ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME};
			default:
				return null;
		}
	}
}

分析AsyncConfigurationSelector源码可知,不同的AdviceMode对应不同的异步配置实现类。这里以ProxyAsyncConfiguration为例分析。

/**
 * {@code @Configuration} class that registers the Spring infrastructure beans necessary
 * to enable proxy-based asynchronous method execution.
 */
@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyAsyncConfiguration extends AbstractAsyncConfiguration {
    
    
	@Bean(name = TaskManagementConfigUtils.ASYNC_ANNOTATION_PROCESSOR_BEAN_NAME)
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public AsyncAnnotationBeanPostProcessor asyncAdvisor() {
    
    
		Assert.notNull(this.enableAsync, "@EnableAsync annotation metadata was not injected");
		AsyncAnnotationBeanPostProcessor bpp = new AsyncAnnotationBeanPostProcessor();
		bpp.configure(this.executor, this.exceptionHandler);
		Class<? extends Annotation> customAsyncAnnotation = this.enableAsync.getClass("annotation");
		if (customAsyncAnnotation != AnnotationUtils.getDefaultValue(EnableAsync.class, "annotation")) {
    
    
			bpp.setAsyncAnnotationType(customAsyncAnnotation);
		}
		bpp.setProxyTargetClass(this.enableAsync.getBoolean("proxyTargetClass"));
		bpp.setOrder(this.enableAsync.<Integer>getNumber("order"));
		return bpp;
	}
}

阅读ProxyAsyncConfiguration源码,其实现逻辑很简单,就是将@EnableAsync注解中配置的参数初始化到AsyncAnnotationBeanPostProcessor实例中。分析AsyncAnnotationBeanPostProcessor源码可知,其背后主要基于Spring AOP实现。Spring AOP的更多介绍实现可以参考笔者之前的博客

总结

通过分析@Async注解相关源码,我们知道了其实现逻辑,也为扩展我们的使用场景提供了技术储备,同时也有助于我们了解Spring AOP的使用案例,为后续Spring AOP的学习做铺垫。

参考

https://www.cnblogs.com/sunshineshen/p/13182324.html @Async 注解的使用
https://cloud.tencent.com/developer/article/1362822 @Async 注解的使用
https://baijiahao.baidu.com/s?id=1726732631392844398&wfr=spider&for=pc @Async的用法和示例
https://www.cnblogs.com/dennyzhangdd/p/9026303.html 异步任务spring @Async注解源码解析
jdk 8 源码

猜你喜欢

转载自blog.csdn.net/wangxufa/article/details/124364846