@Async

  • Spring的@Async注解
  • 手写实现@Async注解



Spring的@Async注解

首先来看看@Async异步注解的使用,它的作用的用的方法变为异步方法,本质就是创建了线程。它相比传统的创建线程的方式,使用@Async有多简洁呢?

先看这个演示,我这是一个Spring Boot项目:
这个@Async注解是直接加在方法上面,这样getStatus()就变成了异步方法

@SpringBootConfiguration
public class AsyncService {
    @Async
    public void getStatus(){
        try {
            Thread.sleep(2000);
            System.out.println("<发送了一份邮件给用户>");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

配置好启动类,要在启动类上加上@EnableAsync注解才能使用。

@SpringBootApplication
@MapperScan("com.symc.mapper")
@EnableAsync
public class App {
    public static void main(String[] args) {
        SpringApplication.run(App.class);
    }
}

那么如何使用这个异步方法呢?很简单,直接调用它:

@RestController
public class AsyncController {
    @Autowired
    private AsyncService asyncService;

    @RequestMapping("/getMessage")
    public String getMessage(){
        System.out.println("<收到了用户的请求>");
        asyncService.getStatus();
        System.out.println("<正在处理用户请求>");
        return "Status Code : 200";
    }
}

尝试访问,观察结果。我们发现正常情况下代码应该是从上往下执行,同步的话浏览器应该是转圈圈等待两秒才能获取结果。而实际直接就返回了结果,这个测试结果说明了getStatus()是异步执行的,它本质是在此处创建了一个新的线程去执行这个方法。

java async 等待 java @async_spring boot


这个注解的原理就相当于下面这段代码的效果,只不过Spring的这个@Async整合了线程池。

@RequestMapping("/getMessage")
    public String getMessage(){
        System.out.println("<收到了用户的请求>");
        new Thread(new Runnable() {
            public void run() {
                asyncService.getStatus();
            }
        }).start();
        System.out.println("<正在处理用户请求>");
        return "Status Code : 200";
    }

手写实现@Async注解

实现方式:使用aop拦截,只要在我们的方法上有使用到@MyAsync单独的开启一个异步线程执行我们的目标方法。所以首先你应该掌握Aop技术,我这里使用Spring的Aop实现。

首先要定义一个注解:com.symc.util.annotation.MyAsync 直接参考复制Spring 的@Async即可。

package com.symc.util.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @Author: Forward Seen
 * @CreateTime: 2022/04/30 21:05
 * @Description: 异步注解
 */

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

然后定义一个切面类,因为要创建一个线程,实现异步的方法应该被包围,所以要使用环绕通知来实现。
通知的目标是注解MyAsync。

package com.symc.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

/**
 * @Author: Forward Seen
 * @CreateTime: 2022/04/30 21:03
 * @Description: MyAsync注解添加代码
 */
@Aspect
@Component
public class ThreadAsyncAop {
    @Around(value = "@annotation(com.symc.util.annotation.MyAsync)")
    public void around(final ProceedingJoinPoint cutPoint){
        new Thread(() -> {
            try {
                cutPoint.proceed();
            } catch (Throwable throwable) {
                throwable.printStackTrace();
            }
        }).start();
    }
}

这样异步注解就算实现了。
可以尝试使用:

@Component
public class AsyncService {
    @MyAsync
    public void getStatus(){
        try {
            Thread.sleep(2000);
            System.out.println("<发送了一份邮件给用户>");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
@RestController
public class AsyncController {
    @Autowired
    private AsyncService asyncService;

    @RequestMapping("/getMessage")
    public String getMessage(){
        System.out.println("<收到了用户的请求>");
        asyncService.getStatus();
        System.out.println("<正在处理用户请求>");
        return "Status Code : 200";
    }
}

用浏览器访问一样能实现上面的打印顺序。

最后我来谈谈这段代码的执行过程(推荐使用Debug观察执行顺序):
首先,我们使用浏览器访问/getMessage,然后服务端就会执行AsyncController.getMessage(),然后执行第一个代码<收到了用户的请求>,接下来要执行asyncService.getStatus();,这时候检查到该方法的注解头上有@MyAsync,因为AOP拦截了该注解,立刻带着AsyncService.getStatus()增强代码,执行拦截方法ThreadAsyncAop.around(),然后创建线程,在该线程中执行cutPoint.proceed();,也就是执行AsyncService.getStatus(),这样该方法就是在一个新的线程中执行,从而达到异步效果。