异步调用的常用三种方式
异步调用是什么:
简单来说,在开发流程中,如果我们从请求发起到数据返回结束,全程从开始到结束是需要等待的,没有其他的额外操作,这个过程可以称之为同步执行过程,在线程角度上,这一个请求任务,是一个单线程任务。
那么异步调用,则是基于这个单线程情况下, 额外增加线程(多线程)进行任务处理。
一般来说,常用的异步调用方式可以分为以下几种。
1、新建多线程
可以直接在你需要异步执行任务的代码处,手动建立新线程(例如new Thread、Executor线程池等方式)。摘抄一个代码:下面这个是线程池的方式(线程池与多线程如何使用可以参考其他文章)
/**
* 定义一个线程池
* 核心线程数(corePoolSize):1
* 最大线程数(maximumPoolSize): 1
* 保持连接时间(keepAliveTime):50000
* 时间单位 (TimeUnit):TimeUnit.MILLISECONDS(毫秒)
* 阻塞队列 new LinkedBlockingQueue<Runnable>()
*/
ThreadPoolExecutor executor = new ThreadPoolExecutor(1,5,50000, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
// 执行业务逻辑方法serviceTest()
executor.execute(new Runnable() {
@Override
public void run() {
asyncService.serviceTest();
}
2、通过配置task
java中,还可以通过配置task至xml中(类似定时任务),在指定的请开启用task,这类方式也是异步调用,但是如果当前情况是,在某一个请求中需要立刻执行异步的话,写入task执行与写入多线程执行的效果就差不多了,代码会比较冗余!
task如何使用这个可以自行百度,不是本次需要描述的重点。
3、通过spring提供的@EnableAsync和@Async注解
那么上面描述的多线程建立的方式,缺点都是:
-- 代码量其实还是较多,如果在项目中存在多个异步方法,那么这种写法会导致很冗余,所以spring提供了一个注解式解决方案。就是Async系列的注解。
使用注解实现异步调用,需要注意2个逻辑步骤:
1)项目中加入@EnableAsync注解
@EnableAsync注解可以放在项目启动类或配置类上(spring boot的主启动类就可以这样使用),这表示当前项目环境支持spring异步启用。
package com.pld.assetpro;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.core.env.Environment;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@SpringBootApplication
@EnableAsync
@Slf4j
public class PldAssetProApplication {
public static void main(String[] args) {
ConfigurableApplicationContext application = SpringApplication.run(PldAssetProApplication.class, args);
Environment env = application.getEnvironment();
...
}
}
2)加入@Async注解
@Async注解可以放置于类上,会表示该类下的所有方法都会是异步的,也可以放置于指定方法上,该方法被调用时会异步执行。
例如:
@Service
@Slf4j
@Async
public class TestForJpaServiceImpl implements ITestForJpaService {
@Override
public void getAsyncMsg() {
log.info("启用异步线程执行方法");
for (int i = 0; i < 100; i++) {
if(i%10 == 0){
System.out.println();
}
System.out.print(i+" ");
}
System.out.println();
}
}
整体代码 --
接口:
package com.pld.assetpro.estate.service;
import org.springframework.scheduling.annotation.Async;
/* 测试下@async和jpa */
public interface ITestForJpaService {
void getAsyncMsg();
}
controller:
package com.pld.assetpro.estate.controller;
import com.pld.assetpro.estate.service.ITestForJpaService;
import com.pld.common.util.MsgResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@Slf4j
@Api(value = "个人自定义测试", tags = {"控制层简单测试"})
@RequestMapping("/testCon/")
public class TestControllerForJpa {
@Autowired
private ITestForJpaService service;
@PostMapping("getMsg")
@ApiOperation(value = "test-jpa获取返回值", notes = "getMsg测试")
public MsgResult<String> getMsg(){
log.info("这是getMsg方法 ------------------ ");
service.getAsyncMsg();
log.info("getMsg方法执行完成");
return MsgResult.successStatic("成功");
}
}
测试结果:可以看到getMsg方法被执行完成后,异步线程执行了service的方法(启用了多线程)。
异步失效情况注意:
通过这类注解执行异步调用,其实底层是通过生成代理对象去操作了多线程(底层就是多线程原理,运用了Executors),所以对应的,会存在一些失效情况(例如同类中,没有加@Async的方法调用了需要异步执行的@Async方法),这类失效情况与@Transactions基本雷同,可以理解为只要被调用对象的代理对象失效,那么方法执行就会变成单线程。
4、MQ
消息队列这里不展开描述,说一下原因。
如果面对高并发等状况,其实上面的异步调用类型中,始终是在同一个服务器下,对请求进行处理,只是开多了线程处理而已,但是还是依旧会占用服务器资源,所以不适用于高并发情况,此时我们可能就需要使用到消息队列等方案去应对了。