Future介绍
对于任务的并行化需求,有些人认为直接通过多线程实现就可以,但是对于多线程,我们在执行的时候会考虑执行顺序问题,还需要接收执行返回的结果,如果等待多个并行任务执行完成后,还需要对任务的结果进行合并,实现多线程的方法有三种:继承Thread类,实现Runnable接口和实现Callable接口。我们在执行的时候会考虑任务的执行顺序,需要等待多个任务执行完成然后合并结果,这其中就会导致下面的问题产生:
- 控制线程的执行顺序,通过join()等待线程结束,但是采用join()的话,会造成阻塞式调用,影响执行的性能,并不能提高并行的执行速率,不过也可以采用wait(),notify(),notifyAll()来实现,这样的话,实现的过程比较复杂,对开发的入门条件比较高,考虑的问题比较多,容易产生bug
- 线程执行完成后,需要收集每个任务的执行结果,由于线程之间的数据安全性,我们就会用到共享变量或线程间的通信等方式来获取执行的结果,这样的话实现结果也比较复杂
为了降低并行编程的难度,Java5推出了Future,就是用于构建复杂并行操作,内部在返回时,不是直接返回数据结果,如图示返回Future对象,通过Future对象来获取结果 其本质是在执行主业务的同时,异步的执行其他分业务,从而利用原本需要同步执行时的等待时间去执行其他的业务,当需要获取其结果时,再进行获取。 Java官方的描述:
Future表示异步计算的结果。 提供了一些方法来检查计算是否完成,等待其完成以及检索计算结果。 只有在计算完成后 才可以使用get方法检索结果,必要时将其阻塞,直到准备就 绪为止。 取消通过cancel方法执行。 提供了其他方法来确 定任务是正常完成还是被取消。 一旦计算完成,就不能取消 计算。
Future接口
在Future接口中,提供了五个接口方法
- boolean cancel(boolean mayInterruptIfRunning);
取消任务, 取消成功返回true;入参mayInterruptIfRunning表示是否允许取消正在执行中的任务
- boolean isCancelled();
是否取消任务:返回布尔值,代表是否取消成功
- boolean isDone();
是否执行完毕:返回布尔值,代表是否执行完毕
- V get() throws InterruptedException, ExecutionException;
获取执行结果:返回Future对象,获取执行结果,如果任务没有完成会阻塞到任务完成再返回
- V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
获取执行结果并设置超时时间,如果超时则抛出TimeoutException
Future应用
Future应用的时候通常会结合ExecutorService来执行,有线程池来开启一个线程执行Future任务
ExecutorService executor = Executors.newCachedThreadPool();
futureA = executor.submit((Callable) () -> getLikeCount());
futureB = executor.submit((Callable) () -> getWatchCount());
futureC = executor.submit((Callable) () -> getForwardCount());
report(futureA, futureB, futureC);
执行结果:
main
pool-1-thread-1
pool-1-thread-3
pool-1-thread-2
main
点赞数:100
阅读量:200
转发数:300
由执行的结果可以看出,三个Future任务采用了三个线程来分别执行,然后生成报表的方法在主线程中完成
在执行的过程中,既有可能将并行变为串行,在最后等待结果的时候,由于其中一个任务某种原因,一直没有返回结果,则future.get()会一直等待任务的结果,咋成后续的逻辑一直无法执行下去,则造成了串行化直接的结果,并没有达到串行的目的。对于这种get一直没有结果返回的情况,我们可以采取get 的另外一个方法,传入等待的时长,如果执行超时就抛出异常,处理异常不阻塞后续的执行流程。 对于Future来说,它能够支持任务并发执行,对于任务结果的获取顺序是按照提交的顺序获取,在使用的过程中建议通过CPU高速轮询的方式获取任务结果,但这种方式比较耗费资源。不建议使用。
FutureTask类
为了整合Fucture和Runable各自的优势,官方在java.util.concurrent包中提供了一个接口RunnableFuture
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}
针对RunnableFuture 接口的实现,官方提供了类:FutureTask类实现,这样任务可以像Future一样交给Executor执行,也可以直接由Runnable中的run执行。其出现的初衷就是为了弥补Thread的不足。
FutureTask使用:
//创建线程池
ExecutorService executorService = Executors.newFixedThreadPool(5);
List<FutureTask<Integer>> taskList = new ArrayList<>();
taskList.add(new FutureTask<>(() -> getLikeCount()));
taskList.add(new FutureTask<>(() -> getWatchCount()));
taskList.add(new FutureTask<>(() -> getForwardCount()));
//将任务提交给线程池执行
taskList.forEach(item -> executorService.submit(item));
//遍历获取结果
for (FutureTask<Integer> futureTask : taskList) {
//FutureTask的get方法会自动阻塞,直到获取计算结果为止
futureTask.get();
}
在实际中FutureTask用的比较少,那是由于获取结果的顺序无法确定,当像Executor提交多个任务并且获取执行完成的结果时,需要循环获取task,然后调用get方法来获取任务的执行结果,但是这样又有老问题存在了:当某个任务执行比较耗时,会将线程阻塞,直到task任务执行完成,也会将并行变成串行。
为了解决Future和FutureTask的问题,Java5还提出了:CompletableFuture这个算是比较好的解决方案,CompletionService的具体使用及原理过程,请关注博主的其他文章,反正这个用了大家都说好。