前言
我们在工作中经常会遇到一个业务需要调用多个系统的http接口,然后所有的结果合并返回。如果我们串行一个接一个的调用,那么随着业务所需接口的增加(调用每个接口都需要一定的时间),耗时也会越来越长,这无疑是不合理的,这时候就需要使用多线程,但是Runnable接口并没有返回值,这时候该怎么办呢?
从jdk 1.5开始,官方就提供了Callable和Future,通过它们可以在任务执行完毕之后得到任务执行结果。
Callable接口
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
要实现Callable接口就需要重写call方法,这是一个泛型接口,返回值类型就是传递进来的V类型。
Callable的call和Runnable的run有着一样的业务定义. 但是本质上是有区别的: call方法有返回值,该方法也可以抛出异常。
Callable一般情况下是配合线程池来使用的
Future
Future接口表示异步计算的结果,通过Future接口提供的方法,可以很方便的查询异步计算任务是否执行完成,获取异步计算的结果,取消未执行的异步任务,或者中断异步任务的执行。
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
FutureTask
但是普通的线程类不能直接传入Callable,这时候就需要使用FutureTask对Callable进行封装
FutureTask定义,该类继承了RunnableFuture接口
public class FutureTask<V> implements RunnableFuture<V>
RunnableFuture定义,该类继承了Runnable接口和Future接口
public interface RunnableFuture<V> extends Runnable, Future<V>
所以FutureTask既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。
使用FutureTask封装Callable很简单只需要在构造对象时传入即可:
new FutureTask<>(callable)
使用示例
下面代码模拟一个业务调用用户信息接口和用户账户信息接口获得用户基本信息和账户信息,最后将结果合并返回。
import java.util.ArrayList;
import java.util.Random;
import java.util.concurrent.*;
/**
* 模拟业务调用多个http接口
*/
public class UserServiceFutureTask {
private Random random = new Random();
/**
* 查询多个系统的数据,合并返回
*/
public ArrayList<String> getUserInfo(String userId) throws ExecutionException, InterruptedException {
// 接口一
Callable<String> callable = new Callable<String>() {
@Override
public String call() throws Exception {
// 1. 先模拟调用获取用户基础信息的http接口
long userinfoTime = System.currentTimeMillis();
int time = random.nextInt(1000);
//模拟接口调用耗时
Thread.sleep(time);
String userInfo = "{\"username\":张三,\"age\":18,\"sex\":\"男\"}";
System.out.println("userinfo-api用户基本信息接口调用时间为" + (System.currentTimeMillis() - userinfoTime)+"毫秒");
return userInfo;
}
};
// 封装成FutureTask
FutureTask<String> userInfoFutureTask = new FutureTask<>(callable);
// 执行业务
new Thread(userInfoFutureTask).start();
// 接口二
FutureTask<String> intergralInfoTask = new FutureTask<>(()->{
// 2. 模拟调用获取用户账户信息的接口
long countApiTime = System.currentTimeMillis();
int time = random.nextInt(1000);
//模拟接口调用耗时
Thread.sleep(time);
String userCount = "{\"countId\":1,\"money\":5000}";
System.out.println("usercount-api账户接口调用时间为" + (System.currentTimeMillis() - countApiTime)+"毫秒");
return userCount;
});
new Thread(intergralInfoTask).start();
// 3. 合并
ArrayList<String> result = new ArrayList<>();
result.add(userInfoFutureTask.get()); // 会阻塞等待任务执行结束
result.add(intergralInfoTask.get());
return result;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
long starttime = System.currentTimeMillis();
UserServiceFutureTask futureTask = new UserServiceFutureTask();
ArrayList<String> userInfo = futureTask.getUserInfo("1");
for (String s : userInfo) {
System.out.println(s);
}
System.out.println("总共耗时:"+(System.currentTimeMillis()-starttime)+"毫秒");
}
}
执行结果: