场景:在实际生活中比如有这么一个需求:需要统计A路的长度和B路长度的总和。
示例一:先测量出来A路的长度,再去测量B路的长度,最后把二者相加;(串行)
示例二:叫一个小弟过来帮忙,自己去测量A路的长度,让小弟去测量B路的长度;(并行)
那么以上这两种示例的测量速度哪个快显而易见,肯定是第二种更快。
示例二中就相当于我们开发中的多线程,线程A和线程B可以同步执行各自的任务,而不是任务B需要等待任务A执行完成后才能开始执行。
在以上例子中,小弟相当于我们开了一个线程B去执行任务,但是如何才能拿到这个线程的返回值呢?这是就引入了Future接口。
Future是一个接口,用于表示异步计算的结果。它提供了一种获取异步计算结果的方法,可以在计算完成之前等待结果的可用性。Future接口有几个常用的方法,包括isDone()用于判断计算是否完成,get()用于获取计算结果(如果计算已完成),cancel()用于取消计算等。
Future的get()方法是一个非常常用方法,用于获取该线程的返回值;
示例代码如下:
ExecutorService executor = Executors.newFixedThreadPool(5);
long start = System.currentTimeMillis();
Future<Integer> future1 = executor.submit(() -> {
Thread.sleep(3000);
return 20;
});
// 获取第一个线程的结果
Integer first = future1.get();
Future<Integer> future2 = executor.submit(() -> {
Thread.sleep(5000);
return 50;
});
// 假设这里还有一段业务代码
Thread.sleep(5000);
// 获取第二个线程的结果
Integer second = future2.get();
long end = System.currentTimeMillis();
System.out.println("两个线程的执行结果为:" + (first + second));
System.out.println("总耗时:" + (end - start) + "ms");
/**
* 结果
* 两个线程的执行结果为:70
* 总耗时:8058ms
*/
需要注意的是线程池中的线程需要实现Callable接口进行返回结果,否则实现Runable接口是没有返回值的
Future中的get()方法有两个实现,源码如下:
/**
* @throws CancellationException {@inheritDoc}
*/
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}
/**
* @throws CancellationException {@inheritDoc}
*/
public V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException {
if (unit == null)
throw new NullPointerException();
int s = state;
if (s <= COMPLETING &&
(s = awaitDone(true, unit.toNanos(timeout))) <= COMPLETING)
throw new TimeoutException();
return report(s);
}
因为get()方法是会阻塞线程直到线程运行结束的,所以为了避免阻塞时间过长,我们可以调用get(long timeout, TimeUnit unit)这个方法来设置一个超时时间,达到超时时间时会抛出TimeoutException()异常。
这也是Future接口的一个弊端,这里可以对上面的代码稍做优化:
ExecutorService executor = Executors.newFixedThreadPool(5);
long start = System.currentTimeMillis();
Future<Integer> future1 = executor.submit(() -> {
Thread.sleep(3000);
return 20;
});
Future<Integer> future2 = executor.submit(() -> {
Thread.sleep(5000);
return 50;
});
// 假设这里还有一段业务代码
Thread.sleep(5000);
// 获取第一个线程的结果
Integer first = future1.get();
// 获取第二个线程的结果
Integer second = future2.get();
long end = System.currentTimeMillis();
System.out.println("两个线程的执行结果为:" + (first + second));
System.out.println("总耗时:" + (end - start) + "ms");
/**
* 结果
* 两个线程的执行结果为:70
* 总耗时:5038ms
*/
可以看到时间差距差了3s,所以,非必要时不要去调用Future的get()方法取值,Furue的其他方法也是同理
除了get外,还有以下方法:
拓展:如果需要判断该线程是否结束,可以调用Futrue的isDone()方法,会返回一个boolean值