1. 为什么要Future
机制
对比常见的两种创建线程的方式,一种是直接继承Thread
,另外一种就是实现Runnable
接口
Runnable
的缺陷
① 在执行完任务之后无法获取执行结果,从Java 1.5开始,就提供了Callable
和Future
,通过它们可以在任务执行完毕之后得到任务执行结果
Runnable
接口中的run
方法本身的返回值是void
② 在run方法中无法抛出checked Exception
,因为覆盖的run方法就没有声明异常,我们只能通过try catch
在run方法内部捕获
为什么Runnable
要这样设计?
异常的抛出总是一层一层的往外抛,最后抛给main方法,对于run方法来说,如果它抛出的异常,那么是由run的使用者也就是Thread类或者线程池来接收异常,而这两个使用者也不是由我们编写的,所以即使抛出还是没法处理异常
2 Callable
public interface Callable<V> {
V call() throws Exception;
}
Callable
的代码也非常简单,不同的是它是一个泛型接口,call()
函数返回的类型就是创建Callable
传进来的V
类型。
学习Callable
对比着Runnable
,这样就很快能理解它。Callable
与Runnable
的功能大致相似,Callable
功能强大一些,就是被线程执行后,可以返回值,并且能抛出异常
3 Future
3.1 基本介绍
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;//超时了要取消任务
}
Callable
和Future
的关系
- 我们可以通过
Future.get()
方法来获取Callable接口返回的执行结果,还可以通过Future.isDone()
来判断任务是否已经执行完成了,以及取消任务,限时获取任务结果等 - 在
call()
未被执行完毕前,调用get()的线程会被阻塞,直到call()
方法返回了结果,此时future.get()
才会拿到结果,主线程切换为runnable
状态 - 所以Future是一个存储器,他存储了
call()
的执行结果,而这个任务的执行时间是无法提前确定的
① V get()
:获取结果
get
方法的行为取决于Callable
任务的状态,有五种情况
- 任务正常完成,
get
方法立刻返回结果 - 任务尚未完成,
get
将阻塞 - 任务执行抛出异常,
get
方法会抛出ExecutionException
,因为get
方法方法签名的异常就是ExecutionException
,所以无论call方法抛出什么异常,get
抛出的都是ExecutionException
- 任务被取消,get方法抛出
CancellationException
- 任务超时,
V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
,抛出TimeoutException
② boolean cancel(boolean mayInterruptIfRunning)
:取消
- 如果这个任务还没开始,那么任务会被正常取消,方法返回
true
- 如果任务已经完成,返回
false
- 如果任务已经开始执行,则取消方法会根据我们传入的
mayInterruptIfRunning
参数做判断
- 传入true表示会发送中断信号中断他
- 传入false则不会发送中断信号,但是不发送中断信号这个方法还有什么用处呢? 用处就在于前两种对方法未执行或者执行完成的判断
cancel(true)
所以,cancel(true)
适用于能处理interrupt的任务cancel(false)
适用于避免启动尚未启动的任务
③ boolean isDone()
异常等情况也是执行结束了
3.2 使用线程池的submit
方法返回Future
对象
首先要给线程池提交任务,提交时线程池会返回给我们一个空的Future
容器,当线程任务执行完毕,线程池就会把结果填入Future
中,之后我们从Future中获取结果
public class FutureDemo {
public static void main(String[] args){
ExecutorService service = Executors.newFixedThreadPool(10);
Future<Integer> future = service.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
return new Random().nextInt();
}
});
try {
System.out.println(future.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
service.shutdown();
}
}
3.3 批量接收结果
public class FutureDemo {
public static void main(String[] args){
ExecutorService service = Executors.newFixedThreadPool(10);
ArrayList<Future> list = new ArrayList<>();
for(int i = 0; i<10; i++){
Future<Integer> future = service.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
return new Random().nextInt();
}
});
list.add(future);
}
for(int i = 0; i<10; i++){
Future<Integer> future = list.get(i);
try {
System.out.println(future.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
service.shutdown();
}
}
3.4 执行时异常
在我们get的时候才会抛出异常
public class FutureDemo {
public static void main(String[] args){
ExecutorService service = Executors.newFixedThreadPool(10);
Future<Integer> future = service.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
throw new Exception();
}
});
try {
System.out.println(future.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
service.shutdown();
}
}
4 FutureTask
可以使用FutureTask
获取Futrue
和任务的结果,FutureTask
是一种包装器,可以把Callable
转化为Future
和Runnable
,他即实现了Runnable
接口,由实现了Future
接口,所以他既可以作为Runnable
被线程执行,又能作为Future
得到Callble
的返回值
FutureTask
实现了RunnableFuture
接口,而RunnableFuture
接口继承自Runnable
和Future
使用
FutureTask task = new FutureTask(new Callable() {
@Override
public String call() throws Exception {
return "hi";
}
});
new Thread(task).start();
System.out.println(task.get());
可以看一下它run方法的源码
public void run() {
if (state != NEW ||
!RUNNER.compareAndSet(this, null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
result = c.call();//我们写的callable
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
if (ran)
set(result);//!!!set了结果,既然能set肯定就能get
}
} finally {
// runner must be non-null until state is settled to
// prevent concurrent calls to run()
runner = null;
// state must be re-read after nulling runner to prevent
// leaked interrupts
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
5. Future的注意点
在上面我们对于Future批量获取线程结果的时候,如果前面的结果比后面的线程慢执行完,那么依旧要等待前面的线程执行完,会带来性功能的损耗,可以使用CompletableFuture改进