1. Runnable的缺陷
- 不能返回一个返回值;
- 不能在方法声明异常;
- 为什么设计成这样:即便是声明异常,也没有处理它的类,所以没必要这么设计;
2. Callable接口
- 类似于Runnable;
- 实现call方法;
- 有返回值;
3. Future类
- 作用:遇到耗时的方法,用子线程去执行,本身去执行其他的事情;
- Callable和Future的关系:可以用Future.get来获取Call的执行结果,还可以利用Future.isDone方法来判断任务是否已经执行完毕,以及取消这个任务,限时获取任务的结果等;
- 当call方法未执行完毕之前,调用get的线程会被阻塞,直到call方法返回了结果后,此时future.get方法才会得到该结果,然后主线程才会切换到RUNNABLE状态;
- 所以Future是一个存储器,存储了call这个任务的结果,而这个任务的执行时间是无法确定的,因为完全取决于call方法执行的情况;
4. Future的主要方法:5个
-
get():获取结果;
-
get(timeout):超时不获取结果;
-
cancel():取消任务的执行;
1. 如果这个任务还没有开始执行,那么这种情况最简单,任务会被正常的取消,未来也不会被执行,方法返回true; 2. 如果任务已完成,或者已取消:那么cancel方法会执行失败,返回false; 3. 如果这个任务已经执行一半了,会根据传入的参数mayInterruptIfRunning判断,true:中断正在运行的线程,false:不进行中断,既然已经执行一半了,就等他执行完毕; cancel(true)适用于任务能处理interrupte的情景;cancel(false)仅仅用于避免启动尚未启动的任务;
-
isDone():判断线程执行完毕;
-
isCancelled():判断任务是不是被取消了;
5. 用法一:用线程池的submit方法提交Callable对象返回Future对象
- 首先,用线程池提交Callable任务,提交时线程池会立刻返回给我们一个空的Future容器。当线程的任务一旦执行完毕,也就是当我们可以获取结果的时候,线程池便会把该结果填入到之前我们的那个Future中去(而不是创建一个新的Future),我们此时就可以从该Future中获得任务执行的结果;
public class FutureCallableDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(10);
Future<Integer> future = executorService.submit(new CallableTask());
System.out.println(future.get());
System.out.println("future等待完毕");
executorService.shutdown();
}
static class CallableTask implements Callable {
@Override
public Integer call() throws Exception {
Thread.sleep(3000);
return new Random().nextInt();
}
}
}
1019197109
future等待完毕
6. 用法二:用FutureTask来创建Future
- 用FutureTask来获取Future和任务的结果;
- FutureTask是一种包装器,可以把Callable转化成Future和Runnable,它同时实现二者的接口;
所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值;
调用方法如下:
- 把Callable实例作为参数,生成FutureTask的对象,此时已经把任务内容传了进入;
- 因为FutureTask实现了Runnable接口,所以可以把这个对象当做一个Runnable对象;
- 用线程池或者另起线程来执行这个FutureTask对象;
- 在启动线程后,FutureTask对象的run方法中会调用Callable对象的call方法返回任务结果;
- 因为FutureTask实现了Future接口,所以可以通过FutureTask获取刚才执行的结果;
public class FutureTaskDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<Callable> callableFutureTask = new FutureTask<Callable>(new CallableTask());
Thread thread = new Thread(callableFutureTask);
thread.start();
System.out.println(callableFutureTask.get());
}
static class CallableTask implements Callable {
@Override
public String call() throws Exception {
return "我是call方法的执行结果";
}
}
}
我是call方法的执行结果
7. Future使用注意点
- Future的生命周期不能后退,就像线程池的生命周期一样,一旦完成了任务,不能重头再来;
- 当for循环批量获取future的结果时,容易发生一部分线程很慢的情况,Future.get()方法一旦发生阻塞,后续的线程要想获取结果也必须得等待,即便是后面的任务已经运行完了,也必须得等第一个线程get到结果才行,可以利用get(timeout)限制,一旦超时就舍弃这个结果(会抛出超时异常);