SpringBoot多线程请求数据之后拼接返回
假设现在有这样一个场景,我需要实现一个接口满足以下功能:
1、需要从A接口取一些数据
2、需要从B接口取一些数据
3、将两个接口取到的数据进行拼接返回给前端。
4、假设第一个接口需要10秒查询时间,第二个接口需要7秒,但是需求是只能在15秒内返回给前端。
如果不用多线程明显会超时,不满足需求。那只能用多线程取分别请求A和B接口的数据,然后把得到的数据进行拼接,返回给前端。
现在有这么几个类
RestRequestDataA,RestRequestDataB,ShowData分别表示从A接口取得的数据,从B接口取得的数据,拼接之后返回给前端的数据。
这三个类的定义如下
class ShowData{
private RestRequestDataA name;
private RestRequestDataB value;
public ShowData(RestRequestDataA name, RestRequestDataB value) {
this.name = name;
this.value = value;
}
@Override
public String toString() {
return "ShowData{" +
"name=" + name.getName() +
", value=" + value.getValue() +
'}';
}
}
class RestRequestDataA{
private String name;
public RestRequestDataA(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
class RestRequestDataB{
private String value;
public RestRequestDataB(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
省了set方法
解决代码
/**
* @author wx
* @date 2021/7/9
*/
public class CompletableFutureTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Date startDate = new Date();
CountDownLatch countDownLatch = new CountDownLatch(2);
FutureTask<RestRequestDataA> futureTask1 = new FutureTask<>(new Callable<RestRequestDataA>() {
@Override
public RestRequestDataA call() throws Exception {
// 假设这里是请求接口A的调用,花费十秒
TimeUnit.SECONDS.sleep(10);
countDownLatch.countDown();
return new RestRequestDataA("myname");
}
});
new Thread(futureTask1).start();
FutureTask<RestRequestDataB> futureTask2 = new FutureTask<>(new Callable<RestRequestDataB>() {
@Override
public RestRequestDataB call() throws Exception {
// 假设这里是请求接口B的调用,花费七秒
TimeUnit.SECONDS.sleep(7);
countDownLatch.countDown();
return new RestRequestDataB("myvalue");
}
});
new Thread(futureTask2).start();
countDownLatch.await();
// A,B接口数据到了,可以开始拼接了
ShowData showData = new ShowData(futureTask1.get(),futureTask2.get());
System.out.println(showData); //拼接之后的数据
Date endDate = new Date();
System.out.println(String.format("一共花费%d毫秒",endDate.getTime()-startDate.getTime()));
}
}
接下来我们来捋一下这个代码
主要是这个四个类:CountDownLatch, FutureTask, Runnable, Future
首先CountDownLatch,latch英文翻译为门栓,这个类的用处在于,确保了A接口和B接口请求的数据都拿到了,再进行拼接。具体我就不展开讲了,可以看这里(传送门)。
Callable是针对于多线程有返回值的时候涉及的类。
FutureTask是实现了RunnableFuture接口,可以看源码
public class FutureTask<V> implements RunnableFuture<V> {
/*
* Revision notes: This differs from previous versions of this
* class that relied on AbstractQueuedSynchronizer, mainly to
* avoid surprising users about retaining interrupt status during
* cancellation races. Sync control in the current design relies
* on a "state" field updated via CAS to track completion, along
* with a simple Treiber stack to hold waiting threads.
*/
RunnableFuture 实现了Runnable接口和Future接口
public interface RunnableFuture<V> extends Runnable, Future<V> {
/**
* Sets this Future to the result of its computation
* unless it has been cancelled.
*/
void run();
}
Runnable这个接口我就不介绍了。
来说一下Future接口,这个类的设计主要是存储多线程还未返回的数据,里面有很多方法,比如:
boolean isDone(); //判断数据是否返回了
boolean isCancelled(); //判断多线程是否取消
V get() throws InterruptedException, ExecutionException; //取数据
....还有很多方法可以自己去看看
最后的效果:
消耗的时间为10秒多一点点,因为拼接也需要时间。
也就是说,通过这种做法可以把请求时间从17秒压缩到10秒(两个接口中返回时间最大的值)。
说在最后,这个问题主要是我在SpringCloud最近用Feign调用其他微服务接口超时了,然后找解决方案,想到可以用这样的方法,springboot中把多线程部分代码改成@Asyn就行,主要是解决思路。
完整代码
package mycode;
import java.util.Date;
import java.util.concurrent.*;
/**
* @author wx
* @date 2021/7/9
*/
public class CompletableFutureTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Date startDate = new Date();
CountDownLatch countDownLatch = new CountDownLatch(2);
FutureTask<RestRequestDataA> futureTask1 = new FutureTask<>(new Callable<RestRequestDataA>() {
@Override
public RestRequestDataA call() throws Exception {
// 假设这里是请求接口A的调用,花费十秒
TimeUnit.SECONDS.sleep(10);
countDownLatch.countDown();
return new RestRequestDataA("myname");
}
});
new Thread(futureTask1).start();
FutureTask<RestRequestDataB> futureTask2 = new FutureTask<>(new Callable<RestRequestDataB>() {
@Override
public RestRequestDataB call() throws Exception {
// 假设这里是请求接口B的调用,花费七秒
TimeUnit.SECONDS.sleep(7);
countDownLatch.countDown();
return new RestRequestDataB("myvalue");
}
});
new Thread(futureTask2).start();
countDownLatch.await();
ShowData showData = new ShowData(futureTask1.get(),futureTask2.get());
System.out.println(showData);
Date endDate = new Date();
System.out.println(String.format("一共花费%d毫秒",endDate.getTime()-startDate.getTime()));
}
}
class ShowData{
private RestRequestDataA name;
private RestRequestDataB value;
public ShowData(RestRequestDataA name, RestRequestDataB value) {
this.name = name;
this.value = value;
}
@Override
public String toString() {
return "ShowData{" +
"name=" + name.getName() +
", value=" + value.getValue() +
'}';
}
}
class RestRequestDataA{
private String name;
public RestRequestDataA(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
class RestRequestDataB{
private String value;
public RestRequestDataB(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}