# 十、Future
# 1. Runnable 的缺点
- 没有返回值。
- 不能抛出 CheckedException。
而 Callable 接口解决了上面这两个问题:
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
# 2. Future
# 2.1 作用
- 让子线程去做一件事,然后自己做自己的事情,需要的时候再来拿子线程的执行结果,然后再做自己的事情。
# 2.2 与 Callable 的关系
我们可以有那个 future.get()
方法来获得 Callable 接口返回的执行结果,还可以通过 future.isDone()
来判断任务是否已经执行完了,以及取消这个任务,限时获取任务的结果等。
在 call()
未执行完毕之前,调用 get()
的线程(假定此时是主线程)会被阻塞,直到 call()
方法返回了结果后,此时 future.get()
才会得到该结果,然后主线程才会切换到 RUNNABLE 状态。
# 2.3 get()
get() 方法的行为取决于 Callable 任务的状态,只有以下 5 种情况:
- 任务正常完成,get() 方法回立刻返回结果。
- 任务尚未开始(任务还没开始或进行中),get() 将阻塞直到任务完成。
- 任务执行过程中抛出
Exception
,get() 方法回抛出ExecutionException
。这里抛出的异常,是 call() 执行时产生的那个异常,看到的异常类型是java.util.concurrent.ExecutionException
。不论 call() 执行时抛出的异常类型是什么,最后 get() 方法抛出的异常类型都是ExecutionException
。 - 任务被取消,get() 方法回抛出
CancellationException
。 - 任务超时,get(long timeout, TimeUnit unit) 会抛出
TimeoutException
。
# 2.4 cancel()
取消任务的执行。
如果这个任务还没有开始执行,那么任务会被正常取消,未来也不会执行,方法返回 true。
如果任务已经完成,或者已取消,那么 cancel() 方法会执行失败,返回 false。
如果任务执行一半了,如果传入 true,那么会中断 task,如果传入 false,那么 task 还会继续进行,返回 true。
cancel(true) 适用于:
- 任务能够处理 interrupt。
cancel(false) 适用于:
- 未能处理 interrupt 的任务。
- 不清楚任务是否支持取消。
- 需要等待已经开始的任务执行完成。
# 2.5 isDone()
判断线程是否执行完毕(完毕不意味着成功)。
# 2.6 isCancelled()
任务是否被取消。
# 3. 示例
# 3.1 用法一
首先,我们需要给线程池提交我们的任务,提交时线程池会立刻返回给我们一个空的 Future 对象。当线程的任务一旦执行完毕,线程池便会把该结果填入之前的 Future 对象中,此时我们便可以通过该对象来获取任务执行的结果。
/**
* 用法1:首先,我们需要给线程池提交我们的任务,提交时线程池会立刻返回给我们一个空的 Future 对象。
* 当线程的任务一旦执行完毕,线程池便会把该结果填入之前的 Future 对象中,
* 此时我们便可以通过该对象来获取任务执行的结果。
*/
public class FutureCase1 {
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(10);
Future<Integer> future = service.submit(new CallableTask());
try {
//获取任务执行结果
System.out.println(future.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
static class CallableTask implements Callable<Integer>{
@Override
public Integer call() throws Exception {
Thread.sleep(3000);
return new Random().nextInt(100);
}
}
}
# 3.2 用法二
多个任务的情况下,我们可以用 Future 数组来获取结果。
/**
* 用法2:多个任务,用 Future 数组来获取结果
*/
public class FutureCase2 {
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(10);
ArrayList<Future<Integer>> futures = new ArrayList<>();
//批量接收结果
for (int i = 0; i < 20; i++) {
Future<Integer> future = service.submit(new CallableTask());
futures.add(future);
}
for (int i = 0; i < 20; i++) {
Future<Integer> integerFuture = futures.get(i);
try {
System.out.println(integerFuture.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
static class CallableTask implements Callable<Integer>{
@Override
public Integer call() throws Exception {
Thread.sleep(3000);
return new Random().nextInt();
}
}
}
# 3.3 用法三
任务执行过程中抛出异常。异常不是直接抛出,而是 get() 执行的时候才抛出异常。
/**
* 用法3:任务执行过程中抛出异常。异常不是直接抛出,而是 **get() 执行的时候才抛出异常。**
*/
public class FutureCase3 {
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(20);
Future<Integer> future = service.submit(new CallableTask());
try {
System.out.println(future.get());
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("抛出InterruptedException");
} catch (ExecutionException e) {
e.printStackTrace();
System.out.println("抛出ExecutionException");
}
System.out.println("任务是否执行结束: " + future.isDone());
}
static class CallableTask implements Callable<Integer> {
@Override
public Integer call() throws Exception {
for (int i = 1; i <= 5; i++) {
System.out.println("第" + i + "次休息");
Thread.sleep(100);
}
throw new IllegalArgumentException("callable 抛出异常...");
}
}
}
结果:
- 只有 get() 执行了才会抛出异常。
- 不管 Task 抛出什么异常,最后捕获的都是 ExecutionException。
- 异常结束 isDone 也是返回 true。
第1次休息
第2次休息
第3次休息
第4次休息
第5次休息
java.util.concurrent.ExecutionException: java.lang.IllegalArgumentException: callable 抛出异常...
at java.util.concurrent.FutureTask.report(FutureTask.java:122)
at java.util.concurrent.FutureTask.get(FutureTask.java:192)
at ch10_future.FutureCase3.main(FutureCase3.java:18)
Caused by: java.lang.IllegalArgumentException: callable 抛出异常...
at ch10_future.FutureCase3$CallableTask.call(FutureCase3.java:37)
at ch10_future.FutureCase3$CallableTask.call(FutureCase3.java:30)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
抛出ExecutionException
任务是否执行结束: true
# 3.4 用法四
演示 get(long time, TimeUnit unit) 的超时情况,需要注意超时后的处理。
cancel(false):不中断 Task
public class FutureCase4 { private static final Ad DEFAULT_AD = new Ad("无网络时候的默认广告"); private static final ExecutorService service = Executors.newFixedThreadPool(5); /** * 广告 */ static class Ad{ String name; public Ad(String name){ this.name = name; } @Override public String toString() { return "Ad{" + "name='" + name + '\'' + '}'; } } static class FetchAdTask implements Callable<Ad>{ @Override public Ad call() throws Exception { try{ Thread.sleep(3000); }catch (InterruptedException e){ System.out.println("sleep期间被中断了"); return new Ad("被中断时的默认广告"); } return new Ad("网络顺畅时的真广告" + new Random().nextInt() + ""); } } public void printAd(){ Future<Ad> future = service.submit(new FetchAdTask()); Ad ad; try { ad = future.get(2000,TimeUnit.MILLISECONDS); } catch (InterruptedException e) { //中断异常 ad = new Ad("被中断时候的默认广告"); } catch (ExecutionException e) { //执行异常 ad = new Ad("执行时异常的默认广告"); } catch (TimeoutException e) { //超时异常 //1.赋值一个默认的 ad = new Ad("超时时的默认广告"); //2. 提醒 System.out.println("超时了,未获取到广告"); //3. 结束任务,传入 false,不中断 task boolean cancel = future.cancel(false); System.out.println("取消成功与否:" + cancel); } service.shutdown(); System.out.println(ad); } public static void main(String[] args) { new FutureCase4().printAd(); } }
结果:
超时了,未获取到广告 取消成功与否:true Ad{name='超时时的默认广告'}
cancel(true):超时后中断 task
public class FutureCase4 { private static final Ad DEFAULT_AD = new Ad("无网络时候的默认广告"); private static final ExecutorService service = Executors.newFixedThreadPool(5); /** * 广告 */ static class Ad{ String name; public Ad(String name){ this.name = name; } @Override public String toString() { return "Ad{" + "name='" + name + '\'' + '}'; } } static class FetchAdTask implements Callable<Ad>{ @Override public Ad call() throws Exception { try{ Thread.sleep(3000); }catch (InterruptedException e){ System.out.println("sleep期间被中断了"); return new Ad("被中断时的默认广告"); } return new Ad("网络顺畅时的真广告" + new Random().nextInt() + ""); } } public void printAd(){ Future<Ad> future = service.submit(new FetchAdTask()); Ad ad; try { ad = future.get(4000,TimeUnit.MILLISECONDS); } catch (InterruptedException e) { //中断异常 ad = new Ad("被中断时候的默认广告"); } catch (ExecutionException e) { //执行异常 ad = new Ad("执行时异常的默认广告"); } catch (TimeoutException e) { //超时异常 //1.赋值一个默认的 ad = new Ad("超时时的默认广告"); //2. 提醒 System.out.println("超时了,未获取到广告"); //3. 结束任务,传入 true,中断 task boolean cancel = future.cancel(true); System.out.println("取消成功与否:" + cancel); } service.shutdown(); System.out.println(ad); } public static void main(String[] args) { new FutureCase4().printAd(); } }
- 结果:
超时了,未获取到广告 sleep期间被中断了 #多了这一句 取消成功与否:true Ad{name='超时时的默认广告'}
# 4. FutureTask
# 4.1 作用
- 用 FutureTask 来获取 Future 和任务的结果。
- FutureTask 是一种包装器,可以把 Callable 转化成 Future 和 Runnable,它同时实现二者的接口。
- FutureTask 既可以作为 Runnable 被线程执行,也可以作为 Future 得到 Callable 的返回值。
# 4.2 使用方式
- 创建一个实现 Callable 的实现类
- 重写 call() 方法,将此线程需要做的事情放在 call() 里面
- 创建 Callable 实现类的对象
- 将上述对象作为参数传递到 FutureTask 构造器中,创建 FutureTask 对象
- 将 FutureTask 对象作为 Runnabke 对象参数传递到 Thread 构造器中,创建 Thread 对象
- 调用 Thread 对象的 start() 启动线程
- 可以通过 FutureTask 对象的 get() 获取 call() 方法的返回值等信息
# 4.3 示例
package ch10_future;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* @author Hedon Wang
* @create 2021-03-25 12:00 PM
*/
public class FutureTaskDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//3. 创建 Callable 实现类的对象
Task task = new Task();
//4. 将上述对象作为参数传递到 FutureTask 构造器中,创建 FutureTask 对象
FutureTask<Integer> futureTask = new FutureTask<>(task);
//5. 将 FutureTask 对象作为 Runnabke 对象参数传递到 Thread 构造器中,创建 Thread 对象
//6. 调用 Thread 对象的 start() 启动线程
new Thread(futureTask).start();
//7. 可以通过 FutureTask 对象的 get() 获取 call() 方法的返回值等信息
Integer res = futureTask.get();
System.out.println("子线程执行结果:" + res);
}
//1. 创建一个实现 Callable 的实现类
static class Task implements Callable<Integer>{
//2. 重写 call() 方法,将此线程需要做的事情放在 call() 里面
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName() + "子线程正在计算");
int sum = 0;
for (int i = 0; i < 100; i++) {
sum += i;
}
return sum;
}
}
}
# 5. 注意点
- 用 for 循环来批量接收任务执行结果的时候,会出现有的快有的慢的情况,后面的得等前面的 get() 执行完毕才能执行。
- Future 的生命周期不能后退(Runnable 可以重复传入 new Thread() 中)。
← 九、AQS 十一、实战:实现高性能缓存 →