Callable、Future和FutureTask是jdk1.5,java.util.concurrent包提供的异步框架,这里再讲一下什么是异步。
异步是指起多个线程,多个线程之间互不干扰,各自执行各自的任务,在代码中可能书写顺序有先有后,但有可能写在后面的线程会比写在前面的线程先执行任务,异步对应并行的概念,常见的异步操作有线程池、Callable、completeFuture等。
同步是多线程里针对竞争现象的一个处理,竞争是指同一时刻有多个线程访问临界资源,可能会引发程序执行错误的结果,同步就是保证某一时刻仅能有一个线程访问临界资源,同步对应串行的概念,常见的同步操作有synchronized关键字、Lock、线程变量、Atomic原子类等。
什么时候需要异步?比如要执行一个任务,该任务执行完后会返回一个结果供其他任务使用,但是该任务很耗时,如果我们把程序设计成串行执行,先执行这个耗时任务,等他结束后再把执行结果给下一个任务使用,这样会耗时,且在这个任务执行期间,其他任务都被阻塞了。我们可以把程序设计成异步,起一个线程执行这个耗时任务,此外主线程做其他事情,等这个耗时任务执行完毕后,主线程再把结果拿到,使用这个结果继续做其他事情,这样在这个耗时任务执行的过程中,主线程可以去做其他事情而不是等他执行完,这样效率会很高,因此异步编程在提高并发量上使用广泛。
今天介绍的三个类/接口,与线程池组合使用,组成了一套java提供的异步框架。
1、Callable接口
先看Callable接口的源码:
@FunctionalInterface
首先是注解是函数式接口,意味着我们可以用lambda表达式更简洁地使用它。Callable是个泛型接口,只有一个方法call,该方法返回类型就是传递进来的V类型。call方法还支持抛出异常.
与Callable对应的是Runnable接口,实现了这两个接口的类都可以当做线程任务递交给线程池执行,Runnable接口的源码如下:
@FunctionalInterface
既然实现了这两个接口的类都可以当做线程任务,这两个接口有啥区别呢?
- Runnable接口是java1.1就有的,Callable接口是java1.5才有的,可以认为Callable接口是升级版的Runnable接口;
- Runnable接口里线程任务是在run方法里写的,Callable接口里线程任务是在call方法里写;
- Callable接口的任务执行后会有返回值,Runnable接口的任务无返回值(void);
- call方法支持抛出异常,run方法不可以;
- 加入线程池运行,Runnable使用ExecutorService的execute方法,Callable使用ExecutorService的submit方法;
- 运行Callable任务可以拿到一个Future对象,表示异步计算的结果。Future对象封装了检查计算是否完成、检索计算的结果的方法,而Runnable接口没有。
第五点提到了Callable使用ExecutorService的submit方法,这里看一下ExecutorService接口里的submit方法的重载情况:
<
常用的是第一个和第三个,这两个方法分别提交实现了Callable接口的类和实现了Runnable接口的类作为线程任务,返回异步计算结果Future,Future里面封装了一些实用方法可以对异步计算结果进行进一步处理。
2、Future
Future是一个接口,封装了异步计算的结果,源码如下:
public
下面对这五个方法介绍:
(1)booleancancel(boolean mayInterruptIfRunning)
参数mayInterruptIfRunning表示是否允许取消正在执行却没有执行完毕的任务,如果设置true,则表示可以取消正在执行过程中的任务。如果任务已经完成,则无论mayInterruptIfRunning为true还是false,此方法肯定返回false,即如果取消已经完成的任务会返回false;如果任务正在执行,若mayInterruptIfRunning设置为true,则返回true,若mayInterruptIfRunning设置为false,则返回false;如果任务还没有执行,则无论mayInterruptIfRunning为true还是false,肯定返回true。(讲道理这我都没细看,用到时再看)。
(2)boolean isCancelled()
isCancelled方法表示任务是否被取消成功,如果在任务正常完成前被取消成功,则返回 true。
(3)boolean isDone()
isDone方法表示任务是否已经完成,若任务完成,则返回true;
(4)V get() throws InterruptedException, ExecutionException
get()方法用来获取执行结果,这个方法会产生阻塞,会一直等到任务执行完毕才返回;
(5) V get(long timeout, TimeUnit unit)throws InterruptedException, ExecutionException, TimeoutException
get(long timeout, TimeUnit unit)用来获取执行结果,如果在指定时间内,还没获取到结果,就直接返回null。
注意两个get方法都会抛出异常。
3、FutureTask
Future是一个接口,FutureTask是Future接口的唯一实现类,一般用的时候向上转型,使用Future。源码如下:
public
FutureTask类实现的是RunnableFuture<V>接口,该接口的源码如下:
public
该接口继承了Runnable接口和Future接口,因此FutureTask类既可以当做线程任务递交给线程池执行,又能当Callable任务的计算结果。
4、使用示例
4.1 Callable + Future
实现Callable接口创建一个异步任务的类,在主线程中起一个线程池执行异步任务,然后在主线程里拿到异步任务的返回结果。
package
执行结果如下:
异步线程任务执行结果 1814400
检查异步线程任务是否执行完毕 true
4.2 Callable + FutureTask
要做的事情跟4.1一样。
package
执行结果与4.1一样。
要注意FutureTask与Future的使用区别,4.2里的FutureTask对象需要一个实现了Callabel接口的类的对象初始化,FutureTask对象向线程池submit的时候没有Future<V>的返回值,而Future有,此外由于FutureTask对象既可以当做线程任务递交给线程池执行,又可以当做Future拿到异步任务的返回结果,因此 FutureTask对象可以直接get得到异步任务的执行结果。
Future与FutureTask的区别:
- Future是一个接口,FutureTask是一个实现类;
- 使用Future初始化一个异步任务结果一般需要搭配线程池的submit,且submit方法有返回值;而初始化一个FutureTask对象需要传入一个实现了Callable接口的类的对象,直接将FutureTask对象submit给线程池,无返回值;
- Future + Callable获取结果需要Future对象的get,而FutureTask获取结果直接用FutureTask对象的get方法即可。
参考
Java并发编程:Callable、Future和FutureTask - 平凡希 - 博客园www.cnblogs.com![2bd8132a584cc1f7720af292f5468832.png](https://i-blog.csdnimg.cn/blog_migrate/284fd177f2bfcfce0ed0afb254343dd8.png)