在开发经常遇到类似这样一种场景:订单支付成功回调中,修改了本地订单记录的状态后,有时需要把状态更新到第三方系统。这时候大多数情况下不需要等待与第三方系统交互的结果就可以直接返回,针对这种场景就可以采用异步编程来实现。
1、同步编程与异步编程的区别
1.1、同步编程
同步是指各个方法在同一线程内按顺序执行,即下一个方法要等待上一个方法执行完成后才开始执行。调用者需要等待所有方法都执行完后才返回。
示例代码如下:
1.1.1、同步方法
@Service
public class AsyncService {
public void method1() throws InterruptedException {
String name = Thread.currentThread().getName();
System.out.println("线程" + name + "执行方法1,需要3s……");
TimeUnit.SECONDS.sleep(3);
}
public void method2() throws InterruptedException {
String name = Thread.currentThread().getName();
System.out.println("线程" + name + "执行方法2,需要2s……");
TimeUnit.SECONDS.sleep(2);
}
public void method3() throws InterruptedException {
String name = Thread.currentThread().getName();
System.out.println("线程" + name + "执行方法3,需要2s……");
TimeUnit.SECONDS.sleep(2);
}
}
1.1.2、调用者
@RestController
@RequestMapping("/async")
public class AsyncController {
@Autowired
private AsyncService asyncService;
@RequestMapping("/test1")
public String test1()throws Exception{
long start = System.currentTimeMillis();
asyncService.method1();
asyncService.method2();
asyncService.method3();
long end = System.currentTimeMillis();
String s = "共消耗:" + (end - start) / 1000 + "s";
System.out.println(s);
return s;
}
}
print:
线程http-nio-8080-exec-1执行方法1,需要3s……
线程http-nio-8080-exec-1执行方法2,需要2s……
线程http-nio-8080-exec-1执行方法3,需要2s……
共消耗:7s
7s后浏览器输出:
1.2、异步编程
异步是指方法另起独立线程执行,即调用者只是发送了调用的指令,无需等待被调用的方法完全执行完毕就返回结果。
示例代码如下:
1.2.1、创建一个简单的线程池
package com.demo.async;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class PoolExecutorService {
private static ExecutorService pool;
public static ExecutorService getExecutorService(){
if(pool == null){
pool = Executors.newFixedThreadPool(10);//线程池大小设置10
}
return pool;
}
}
1.2.2、调用者
此处还是继续用上面1.1.1的同步方法,只是手动起独立线程调用。
@RestController
@RequestMapping("/async")
public class AsyncController {
@Autowired
private AsyncService asyncService;
@RequestMapping("/test2")
public String test2()throws Exception{
// 创建线程池
ExecutorService pool = PoolExecutorService.getExecutorService();
long start = System.currentTimeMillis();
pool.execute(new Runnable() {
@Override
public void run() {
try {
asyncService.method1();
} catch (Exception e) {
e.printStackTrace();
}
}
});
pool.execute(new Runnable() {
@Override
public void run() {
try {
asyncService.method2();
} catch (Exception e) {
e.printStackTrace();
}
}
});
pool.execute(new Runnable() {
@Override
public void run() {
try {
asyncService.method3();
} catch (Exception e) {
e.printStackTrace();
}
}
});
long end = System.currentTimeMillis();
String s = "共消耗:" + (end - start) / 1000 + "s";
System.out.println(s);
return s;
}
}
print:
线程pool-1-thread-1执行方法1,需要3s……
线程pool-1-thread-2执行方法2,需要2s……
共消耗:0s
线程pool-1-thread-3执行方法3,需要2s……
无需等待,浏览器立即输出如下:
2、@Async注解
在spring 3.x之后,提供了@Async注解,被@Async标注的方法,称为异步方法,这些方法在执行时,Spring会为它们自动创建独立线程,而不用像上面1.2.2一样手动起线程。
2.1、启用@Async
2.1.1、Sprint Boot项目
若是Sprint Boot项目,在启动类上加@EnableAsync注解即可。
package com.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
@EnableAsync
@SpringBootApplication
public class App {
public static void main( String[] args ) {
SpringApplication.run(App.class, args);
}
}
2.1.2、Java-web项目
若是Java-web项目则在ApplicationContext.xml中写如下配置:
<task:executor id="myexecutor" pool-size="10" />
<task:annotation-driven executor="myexecutor"/>
2.2、异步方法
在方法上加@Async注解
@Service
public class AsyncService {
@Async
public void asyncMethod1() throws InterruptedException {
String name = Thread.currentThread().getName();
System.out.println("线程" + name + "执行异步方法1,需要3s……");
TimeUnit.SECONDS.sleep(3);
}
@Async
public void asyncMethod2() throws InterruptedException {
String name = Thread.currentThread().getName();
System.out.println("线程" + name + "执行异步方法2,需要3s……");
TimeUnit.SECONDS.sleep(3);
}
}
2.3、调用者
在调用者中和正常方法一样地调用,不必手动另起独立线程。
@RestController
@RequestMapping("/async")
public class AsyncController {
@Autowired
private AsyncService asyncService;
@RequestMapping("/test3")
public String test3()throws Exception{
long start = System.currentTimeMillis();
asyncService.asyncMethod1();
asyncService.asyncMethod2();
long end = System.currentTimeMillis();
String s = "共消耗:" + (end - start) / 1000 + "s";
System.out.println(s);
return s;
}
}
print:
共消耗:0s
线程task-2执行异步方法2,需要3s……
线程task-1执行异步方法1,需要3s……
浏览器输出:
2.4、有返回值调用
@Async在大多数业务场景都是用于无返回值的调用,当需要获取异步方法返回值时,可以返回AsyncResult类型的结果。
2.4.1、异步方法
@Service
public class AsyncService {
@Async
public Future<String> asyncMethod3() throws InterruptedException {
System.out.println("执行异步方法3,需要3s……");
TimeUnit.SECONDS.sleep(3);
return new AsyncResult<>("异步方法3执行完成!");
}
}
2.4.2、调用者
@RestController
@RequestMapping("/async")
public class AsyncController {
@Autowired
private AsyncService asyncService;
@RequestMapping("/test4")
public String test4()throws Exception{
Future<String> future = asyncService.asyncMethod3();
String s = "";
while (true){//这里使用了循环判断,等待获取结果信息
if (future.isDone()){//判断是否执行完毕
s = future.get();
break;
}
System.out.println("Continue doing something else...");
Thread.sleep(1000);
}
return s;
}
}
print:
Continue doing something else...
执行异步方法3,需要3s……
Continue doing something else...
Continue doing something else...
Continue doing something else...
等待3s后,浏览器输出: