Spring @Async 注解的使用以及原理(一)

    Spring中用@Async注解标记的方法,称为异步方法,它会在调用方的当前线程之外的独立的线程中执行,其实就相当于我们自己new Thread(()-> System.out.println("hello world !"))这样在另一个线程中去执行相应的业务逻辑。本篇先只讲@Async的使用,后面会分析它实现原理。

     @Async注解使用条件:

  1. @Async注解一般用在类的方法上,如果用在类上,那么这个类所有的方法都是异步执行的;
  2. 所使用的@Async注解方法的类对象应该是Spring容器管理的bean对象;
  3. 调用异步方法类上需要配置上注解@EnableAsync

使用注意:

1、默认情况下(即@EnableAsync注解的mode=AdviceMode.PROXY),同一个类内部没有使用@Async注解修饰的方法调用@Async注解修饰的方法,是不会异步执行的,这点跟 @Transitional 注解类似,底层都是通过动态代理实现的。如果想实现类内部自调用也可以异步,则需要切换@EnableAsync注解的mode=AdviceMode.ASPECTJ,详见@EnableAsync注解。

2、任意参数类型都是支持的,但是方法返回值必须是void或者Future类型。当使用Future时,你可以使用 实现了Future接口的ListenableFuture接口或者CompletableFuture类与异步任务做更好的交互。如果异步方法有返回值,没有使用Future<V>类型的话,调用方获取不到返回值。

@Async 源码

       我们用 spring-context-5.0.5.RELEASE.jar 包,先看一下 @Async 的源码:

/**
 * Annotation that marks a method as a candidate for <i>asynchronous</i> execution.
 * Can also be used at the type level, in which case all of the type's methods are
 * considered as asynchronous.
 *(该注解标记一个异步执行的方法,用在类上,那么类所有的方法都是异步执行的。)
 *
 * <p>In terms of target method signatures, any parameter types are supported.
 * However, the return type is constrained to either {@code void} or
 * {@link java.util.concurrent.Future}. In the latter case, you may declare the
 * more specific {@link org.springframework.util.concurrent.ListenableFuture} or
 * {@link java.util.concurrent.CompletableFuture} types which allow for richer
 * interaction with the asynchronous task and for immediate composition with
 * further processing steps.
 * (任意参数类型都是支持的,但是方法返回值必须是void或者Future类型。当使用Future时,你可以使用 
 * 实现了Future接口的ListenableFuture接口或者CompletableFuture类与异步任务做更好的交互。)
 *
 * <p>A {@code Future} handle returned from the proxy will be an actual asynchronous
 * {@code Future} that can be used to track the result of the asynchronous method
 * execution. However, since the target method needs to implement the same signature,
 * it will have to return a temporary {@code Future} handle that just passes a value
 * through: e.g. Spring's {@link AsyncResult}, EJB 3.1's {@link javax.ejb.AsyncResult},
 * or {@link java.util.concurrent.CompletableFuture#completedFuture(Object)}.
 *
 * @author Juergen Hoeller
 * @author Chris Beams
 * @since 3.0
 * @see AnnotationAsyncExecutionInterceptor
 * @see AsyncAnnotationAdvisor
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@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 this
	 * method, 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.
     * (可以匹配自定义的任务执行器-->Executor类型或者TaskExecutor类型的bean)
	 * <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 "";

}

       阅读@Async的源码以及其中涉及的Executor、TaskExecutor 等类,我们大致就可以推测出@Async底层用的估计是线程池技术,事实也正是如此。

测试:

     我们在Spring Boot项目中做一下测试,测试代码如下:

异步方法定义以及实现如下:

package com.example.service;

import org.springframework.scheduling.annotation.Async;


public interface TestService {
    @Async
    void test();
}
package com.example.service.impl;

import com.example.service.TestService;
import org.springframework.stereotype.Service;

@Service
public class TestServiceImpl implements TestService {
    @Override
    public void test() {
        System.out.println("ThreadName:" + Thread.currentThread().getName());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("测试Spring 异步调用!");
    }
}

 调用异步方法的controller类代码如下:

package com.example.demo;

import com.example.model.PramInfo;
import com.example.service.TestService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.web.bind.annotation.*;


@RestController
@RequestMapping(value = "/test")
@EnableAsync
public class TestContoller {

    @Autowired
    private TestService testService;

    @GetMapping(value = "/testAsync")
    public void print() {
        System.out.println("ThreadName:" + Thread.currentThread().getName());
        System.out.println("当前线程开始执行测试函数......");
        testService.test();
        for (int i = 1; i <= 100; i++) {
            System.out.print(i + " ");
            if (i % 10 == 0) {
                System.out.println();
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        System.out.println("当前线程测试函数执行完毕......");
    }
}

程序启动后,浏览器访问http://localhost:8080/test/testAsync,后台打印日志如下:

ThreadName:http-nio-8080-exec-5
当前线程开始执行测试函数......
1 2 3 4 5 6 7 8 9 10 
ThreadName:SimpleAsyncTaskExecutor-2
测试Spring 异步调用!
11 12 13 14 15 16 17 18 19 20 
21 22 23 24 25 26 27 28 29 30 
31 32 33 34 35 36 37 38 39 40 
41 42 43 44 45 46 47 48 49 50 
51 52 53 54 55 56 57 58 59 60 
61 62 63 64 65 66 67 68 69 70 
71 72 73 74 75 76 77 78 79 80 
81 82 83 84 85 86 87 88 89 90 
91 92 93 94 95 96 97 98 99 100 
当前线程测试函数执行完毕......

可以看出是有两个线程在执行。

debugger可以看到,testService已经被替换成了动态代理生成的bean:

如果我们去除TestController上的@EnableAsync或者new 一个TestService对象(该对象没有加载进Spring的容器中),那么TestController中的print()方法都会同步执行,后台打印日志也可以看到只有一个线程在执行:

ThreadName:http-nio-8080-exec-5
当前线程开始执行测试函数......
ThreadName:http-nio-8080-exec-5
测试Spring 异步调用!
1 2 3 4 5 6 7 8 9 10 
11 12 13 14 15 16 17 18 19 20 
21 22 23 24 25 26 27 28 29 30 
31 32 33 34 35 36 37 38 39 40 
41 42 43 44 45 46 47 48 49 50 
51 52 53 54 55 56 57 58 59 60 
61 62 63 64 65 66 67 68 69 70 
71 72 73 74 75 76 77 78 79 80 
81 82 83 84 85 86 87 88 89 90 
91 92 93 94 95 96 97 98 99 100 
当前线程测试函数执行完毕......

 

如果我们把TestService做一些简单修改,@Async 修饰test()方法,test2()方法不修饰:

public interface TestService {
    @Async
    void test();

    void test2();
}

public class TestServiceImpl implements TestService{
    @Override
    public void test() {
        System.out.println("ThreadName:" + Thread.currentThread().getName());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("测试Spring 异步调用!");
    }

    @Override
    public void test2() {
        test();//自调用test()方法
    }
}

controller类:

@GetMapping(value = "/testAsync")
    public void print() {
        System.out.println("ThreadName:" + Thread.currentThread().getName());
        System.out.println("当前线程开始执行测试函数......");
        testService.test2();//改成调用没有@Async注解修饰的test2()方法
        for (int i = 1; i <= 100; i++) {
            System.out.print(i + " ");
            if (i % 10 == 0) {
                System.out.println();
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        System.out.println("当前线程测试函数执行完毕......");
    }

这个时候能看到测试结果,test2()方法调用的test()方法并没有异步执行,只有一个线程,如下:

ThreadName:http-nio-8080-exec-1
当前线程开始执行测试函数......
ThreadName:http-nio-8080-exec-1
测试Spring 异步调用!
1 2 3 4 5 6 7 8 9 10 
11 12 13 14 15 16 17 18 19 20 
21 22 23 24 25 26 27 28 29 30 
31 32 33 34 35 36 37 38 39 40 
41 42 43 44 45 46 47 48 49 50 
51 52 53 54 55 56 57 58 59 60 

 

后面会总结分析@Async注解的实现原理以及配置我们自己定义的执行器,还有就是分析异步情况下的异常处理和事务处理等。

  • 50
    点赞
  • 239
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值