1. 问题
一个用户请求需要请求两个接口,每个接口耗时1s,那么整体的响应时间应该是2s,如果我们想让请求在1s就返回给用户怎么做呢?
通过我们之前几篇文章的学习,实现这个功能很简单,只需要开启一个新线程处理一个接口,主线程处理一个接口,两个接口都返回后钻皇数据返回给用户就可以了。
由于Thread或Runnable并不能返回结果,你可能会这样写代码
final String[] res1 = {null};
final String[] res2 = {null};
new Thread() {
@Override
public void run() {
String result = "远程调用耗时的方法";
res1[0] = result;
}
}.start();
new Thread() {
@Override
public void run() {
String result = "远程调用耗时的方法";
res2[0] = result;
}
}.start();
while (res1[0] != null && res2[0] != null) {
//.....组装数据并返回
}
虽然能解决问题但是这个写法也太。。。。。。
幸好jdk给我们封装了一种Future模式
2. Future
Callable接口,与Runable接口相比它可以返回数据
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;
}
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;
}
3. Callable案例演示
public class ThreadPoolTest {
public static void main(String[] args) throws Exception {
ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1,
1L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(),
new ThreadPoolExecutor.DiscardPolicy());
long start = System.currentTimeMillis();
Callable<String> callable = new Callable<String>() {
@Override
public String call() throws InterruptedException {
Thread.sleep(1000L);
return "远程调用耗时的方法";
}
};
Future<?> future = executor.submit(callable);
Thread.sleep(1000L);
Object s = future.get();
System.out.println(s);
long end = System.currentTimeMillis();
System.out.println("共耗时:" + (end - start) + "ms");
executor.shutdown();
}
}
执行结果:
远程调用耗时的方法
共耗时:1034ms
4. FutureTask案例演示
public class ThreadPoolTest {
public static void main(String[] args) throws Exception {
ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 3,
1L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(),
new ThreadPoolExecutor.DiscardPolicy());
long start = System.currentTimeMillis();
FutureTask<String> futureTask = new FutureTask<>(new Callable<String>() {
@Override
public String call() throws Exception {
Thread.sleep(1000L);
return "远程调用耗时的方法";
}
});
executor.submit(futureTask);
Thread.sleep(1000L);
Object s = futureTask.get();
System.out.println(s);
long end = System.currentTimeMillis();
System.out.println("共耗时:" + (end - start) + "ms");
executor.shutdown();
}
}
执行结果:
远程调用耗时的方法
共耗时:1034ms
5. jdk-Future模式原理
我们从入口开始看,看一下线程池submit方法:
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
任务提交后,任务被封装成一个RunnableFuture对象
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
return new FutureTask<T>(runnable, value);
}
使用的是FutureTask类,此类实现了Runnable和Future接口
public class FutureTask<V> implements RunnableFuture<V>
FutureTask类调用get方法时会阻塞直到拿到结果:
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}
下面看一下awaitDone()方法
这里会循环判断state,state是一个volatile状态变量,当线程执行完毕会改变他的状态为完成状态,否则get方法会一直阻塞
private volatile int state;
private static final int NEW = 0;
private static final int COMPLETING = 1;
private static final int NORMAL = 2;
private static final int EXCEPTIONAL = 3;
private static final int CANCELLED = 4;
private static final int INTERRUPTING = 5;
private static final int INTERRUPTED = 6;
其实原理和我们第一步写的代码是类似的,他只不过把循环判断状态的事情封装了一下,定义了一个可以返回结果的Future接口类来做这个事情。