多线程异步回调技术
一、任务代码
1.1 需要执行的任务类
@Component
public class Task {
public String doTaskOne() throws Exception {
long start = System.currentTimeMillis();
Thread.sleep(1000);
long end = System.currentTimeMillis();
System.out.println("任务一耗时:" + (end - start) + "毫秒");
return "任务一的执行结果";
}
public String doTaskTwo() throws InterruptedException {
long start = System.currentTimeMillis();
Thread.sleep(2000);
long end = System.currentTimeMillis();
System.out.println("任务二耗时:" + (end - start) + "毫秒");
return "任务二的执行结果";
}
public String doTaskThree() throws InterruptedException {
long start = System.currentTimeMillis();
Thread.sleep(3000);
long end = System.currentTimeMillis();
System.out.println("任务三耗时:" + (end - start) + "毫秒");
return "任务三的执行结果";
}
public String doTaskFour() throws InterruptedException {
long start = System.currentTimeMillis();
Thread.sleep(4000);
long end = System.currentTimeMillis();
System.out.println("任务四耗时:" + (end - start) + "毫秒");
return "任务四的执行结果";
}
}
1.2 执行任务的业务类
@Service
public class TaskServiceImpl implements ITaskService {
@Autowired
private Task task;
public Map<String, Object> doTaskMethod() throws Exception {
//执行任务
String taskOneResult = task.doTaskOne();
String taskTwoResult = task.doTaskTwo();
String taskThreeResult = task.doTaskThree();
String taskFourResult = task.doTaskFour();
//返回结果集
Map<String, Object> resultMap = new HashMap<>();
resultMap.put("taskOneResult", taskOneResult);
resultMap.put("taskTwoResult", taskTwoResult);
resultMap.put("taskThreeResult", taskThreeResult);
resultMap.put("taskFourResult", taskFourResult);
return resultMap;
}
}
1.3 测试入口(基于SpringBoot)
@SpringBootTest(classes = {Application.class})
public class TestThread {
@Autowired
private TaskServiceImpl taskService;
@Test
public void test() Exception {
long start = System.currentTimeMillis();
Map<String, Object> result = taskService.doTaskMethod();
long end = System.currentTimeMillis();
System.out.println("任务总耗时:" + (end - start) + "毫秒");
System.out.println("执行结果为:");
System.out.println(result);
}
}
测试入口的代码五轮使用哪种方式都无需改造。
执行结果如下:
发现执行四个任务需要的时间为10秒钟,因为所有任务都是依次执行。
平常业务开发中可能经常遇见这种情况,导致查询接口执行特别慢。
我们可以使用多线程异步回调的技术来优化代码,在无法优化sql
语句时提升查询性能。
二、原生Java方式
2.1 使用jdk8新增的CompletableFuture类来优化上面的代码
@Service
public class TaskServiceImpl {
@Autowired
private Task task;
public Map<String, Object> doTaskMethod() throws Exception {
//创建四个CompletableFuture对象,并把四个多线程任务作为参数传递进去
CompletableFuture<String> completableFutureOne = CompletableFuture.supplyAsync(
() -> {
String taskResult = null;
try {
taskResult = task.doTaskOne();
} catch (InterruptedException e) {
e.printStackTrace();
}
return taskResult;
});
CompletableFuture<String> completableFutureTwo = CompletableFuture.supplyAsync(
() -> {
String taskResult = null;
try {
taskResult = task.doTaskTwo();
} catch (InterruptedException e) {
e.printStackTrace();
}
return taskResult;
});
CompletableFuture<String> completableFutureThree = CompletableFuture.supplyAsync(
() -> {
String taskResult = null;
try {
taskResult = task.doTaskThree();
} catch (InterruptedException e) {
e.printStackTrace();
}
return taskResult;
});
CompletableFuture<String> completableFutureFour = CompletableFuture.supplyAsync(
() -> {
String taskResult = null;
try {
taskResult = task.doTaskFour();
} catch (InterruptedException e) {
e.printStackTrace();
}
return taskResult;
});
//依次获得任务的执行结果 并返回结果集
Map<String, Object> resultMap = new HashMap<>();
resultMap.put("taskOneResult", CompletableFutureOne.get());
resultMap.put("taskTwoResult", CompletableFutureTwo.get());
resultMap.put("taskThreeResult", CompletableFutureThree.get());
resultMap.put("taskFourResult", CompletableFutureFour.get());
//如果不考虑获取各任务返回结果,只需要四个异步任务执行完毕即可的话。可以使用如下代码
//join()必须要写,它可以让线程阻塞在这,等待四个任务都执行完毕
//要不然任务都没执行完就return resultMap了
//CompletableFuture.allOf(task1,task2,task3,task4).join();
return resultMap;
}
}
api解释:
1) 关于lambda表达式创建多线程的解释
创建一个多线程可以使用如下lambda
表达式的方式:
Runnable runnable = () -> System.out.println("任务的执行结果");
这个表达式就相当于创建了一个Runnable
接口的实现类。->
右侧就是run
方法的内容。
2) 关于CompletableFuture的解释
字面意思就是可完成的Future
。主要是为了解决jdk8
之前的Future
类带来的弊端。
3) 关于CompletableFuture.supplyAsync()的解释
supplyAsync
方法用于创建一个CompletableFuture
对象,它的参数接收一个Supplier
函数式接口。
该方法创建的CompletableFuture
对象会异步执行当前传入的计算任务。
在调用端,可以通过get或join获取任务执行的结果。
4) 关于CompletableFutureOne.get()的解释
可以获取CompletableFuture
对象中异步任务的执行结果。
5) 关于CompletableFutureOne.allOf()的解释
异步执行参数里面的异步任务。
6)join()的作用
它可以让线程阻塞在这,等待allOf
中的任务都执行完毕
2.2 执行结果
使用多线程加上CompletableFuture
优化后,任务的执行的总时间几乎和任务四的执行时间一致。
大大缩短了任务执行的时间。
同时也能依次获取到异步任务的结果。可以说是非常厉害了。
但是发现上面的代码还是比较的冗余。有没有办法再优化一下。
二、SpringBoot+@Async方式
2.1 开启@Async支持
在SpringBoot
启动类上加@EnableAsync
注解开启@Async
支持
@SpringBootApplication
@EnableAsync
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
2.2 改造Task类
@Component
public class Task {
@Async
public CompletableFuture doTaskOne() throws InterruptedException {
long start = System.currentTimeMillis();
Thread.sleep(1000);
long end = System.currentTimeMillis();
System.out.println("任务一耗时:" + (end - start) + "毫秒");
return CompletableFuture.completedFuture("任务一的执行结果");
}
@Async
public CompletableFuture doTaskTwo() throws InterruptedException {
long start = System.currentTimeMillis();
Thread.sleep(2000);
long end = System.currentTimeMillis();
System.out.println("任务二耗时:" + (end - start) + "毫秒");
return CompletableFuture.completedFuture("任务二的执行结果");
}
@Async
public CompletableFuture doTaskThree() throws InterruptedException {
long start = System.currentTimeMillis();
Thread.sleep(3000);
long end = System.currentTimeMillis();
System.out.println("任务三耗时:" + (end - start) + "毫秒");
return CompletableFuture.completedFuture("任务三的执行结果");
}
@Async
public CompletableFuture doTaskFour() throws InterruptedException {
long start = System.currentTimeMillis();
Thread.sleep(4000);
long end = System.currentTimeMillis();
System.out.println("任务四耗时:" + (end - start) + "毫秒");
return CompletableFuture.completedFuture("任务四的执行结果");
}
}
api解释:
1) 关于@Async
@Async
注解能将原来的同步方法变为异步方法。
2) 关于CompletableFuture.completedFuture的解释
completedFuture
方法是一个静态方法,作用也是创建一个CompletableFuture
对象。
源码如下:
public static <U> CompletableFuture<U> completedFuture(U value) {
return new CompletableFuture<U>((value == null) ? NIL : value);
}
2.3 改造TaskServiceImpl
@Service
public class TaskServiceImpl {
@Autowired
private Task task;
public Map<String, Object> doTaskMethod() throws Exception {
//执行任务
CompletableFuture completableFutureOne = task.doTaskOne();
CompletableFuture completableFutureTwo = task.doTaskTwo();
CompletableFuture completableFutureThree = task.doTaskThree();
CompletableFuture completableFutureFour = task.doTaskFour();
//返回结果集
Map<String, Object> resultMap = new HashMap<>();
resultMap.put("taskOneResult", completableFutureOne.get());
resultMap.put("taskTwoResult", completableFutureTwo.get());
resultMap.put("taskThreeResult", completableFutureThree.get());
resultMap.put("taskFourResult", completableFutureFour.get());
return resultMap;
}
}
此时doTaskMethod
中就已经清爽很多了。
2.4 执行结果
发现依旧保持了异步任务的高性能,又让代码更加简洁。还能获取到异步任务的结果。
但是大量使用这种异步任务,对服务器的要求是比较高的。
所以开发中并不需要把所有任务都写成这个样子。
只需要对和上面这些情况类似的情况,就可以使用异步回调的方式来优化。