Java多线程学习笔记:Callable、Future、FutureTask
概述
创建线程的三种方式:
- 继承Thread,重写run方法
- 实现Runnable接口,重新run方法
- 实现Callable接口,重写call方法
前两种方式,一种是直接继承Thread,另外一种就是实现Runnable接口,这两种方式都是Java第一版就有的方法。
这两种方式都有一个缺陷就是:在执行完任务之后无法获取执行结果。
如果需要获取执行结果,就必须通过共享变量或者使用线程通信的方式来达到效果,这样使用起来就比较麻烦。
而自从Java 1.5开始,就提供了Callable和Future,通过它们可以在任务执行完毕之后得到任务执行结果。
Callable接口实际上是属于Executor框架中的功能类,Callable接口与Runnable接口的功能类似,但提供了比Runnable更加强大的功能。
- Callable可以在任务结束的时候提供一个返回值,Runnable无法提供这个功能
- Callable的call方法分可以抛出异常,而Runnable的run方法不能抛出异常。
什么是Executor
Executor仅仅是一个接口,只有一个方法execute(Runnable command),是在JDK1.5中引入的,主要是用来运行提交的可运行的任务。一般我们并不直接使用该接口,而是使用其不同的子接口,主要是ExecutorService,而通常情况,与ExecutorService一起使用的是Executors类,该类由著名的并发编程大师Doug Lea实现。Executor框架可以用来控制线程的启动、执行和关闭,可以简化并发编程的操作。
什么是ExecutorService
常用接口分析
在ExecutorService中提供了重载的submit()方法,该方法既可以接收Runnable实例又能接收Callable实例。对于实现Callable接口的类,需要覆写call()方法,并且只能通过ExecutorService的submit()方法来启动call()方法。那么既然存在Runnable接口,为什么还要添加Callable接口呢?这是因为Runnable不会返回结果,并且无法抛出经过检查的异常而Callable会返回结果,而且当获取返回结果时可能会抛出异常。Callable中的call()方法类似Runnable的run()方法,区别同样前者有返回值,而后者没有。
什么是Executors
Executors提供了一系列工厂方法用于创建线程池,返回的线程池都实现了ExecutorService接口:
- newCachedThreadPool()可缓存线程池,对于每个线程,如果有空闲线程可用,立即让它执行,如果没有,则创建一个新线程
- newFixedThreadPool()具有固定大小的线程池,如果任务数大于空闲的线程数,则把它们放进队列中等待
- newSingleThreadPool()大小为1的线程池,任务一个接着一个完成
- newScheduledThreadPool(int)调度型线程池,可控制线程最大并发数,支持定时及周期性任务执行,用来代替Timer
Callable
Callable接口使用泛型去定义它的返回类型。Executors类提供了一些有用的方法去在线程池中执行Callable内的任务。由于Callable任务是并行的,我们必须等待它返回的结果。java.util.concurrent.Future对象为我们解决了这个问题。在线程池提交Callable任务后返回了一个Future对象,使用它我们可以知道Callable任务的状态和得到Callable返回的执行结果。Future提供了get()方法让我们可以等待Callable结束并获取它的执行结果。
继承关系
常用方法
public interface Callable<V> {
V call() throws Exception;
}
Callable的代码也非常简单,不同的是它是一个泛型接口,call()函数返回的类型就是创建Callable传进来的V类型。
学习Callable对比着Runnable。Callable与Runnable的功能大致相似,Callable功能强大一些,就是被线程执行后,可以返回值,并且能抛出异常。
Future
Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。可以通过get()方法获取执行结果,该方法会阻塞直到任务返回结果。
Future类位于java.util.concurrent包下,它是一个接口
常用方法
在Future接口中声明了5个方法,下面依次解释每个方法的作用:
- cancel(boolean mayInterruptIfRunning)方法用来取消任务,如果取消任务成功则返回true,如果取消任务失败则返回false。参数mayInterruptIfRunning表示是否允许取消正在执行却没有执行完毕的任务,如果设置true,则表示可以取消正在执行过程中的任务。如果任务已经完成,则无论mayInterruptIfRunning为true还是false,此方法肯定返回false,即如果取消已经完成的任务会返回false;如果任务正在执行,若mayInterruptIfRunning设置为true,则返回true,若mayInterruptIfRunning设置为false,则返回false;如果任务还没有执行,则无论mayInterruptIfRunning为true还是false,肯定返回true。
- isCancelled()方法表示任务是否被取消成功,如果在任务正常完成前被取消成功,则返回 true。
- isDone()方法表示任务是否已经完成,若任务完成,则返回true;
- get()方法用来获取执行结果,这个方法会产生阻塞,会一直等到任务执行完毕才返回;
- get(long timeout, TimeUnit unit)用来获取执行结果,如果在指定时间内,还没获取到结果,就直接返回null。
也就是说Future提供了三种功能:
- 判断任务是否完成
- 能够中断任务
- 能够获取任务执行结果
Callable+Future代码
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newCachedThreadPool();
Future<String> future = executorService.submit(new MyCallable());
executorService.shutdown();
System.out.println("do something in main");
System.out.println("得到异步任务返回结果:" + future.get());
System.out.println("主线程结束");
}
static class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("Callable子线程开始");
Thread.sleep(2000); // 模拟做点事情
System.out.println("Callable子线程结束");
return "MyCallable return";
}
}
运行结果
上面是Future基本用法的代码以及并运行,我们可以知道:
- 线程是属于异步计算模型,所以你不可能直接从别的线程中得到方法返回值。 这时候,Future就出场了。
- Futrue可以监视目标线程调用call的情况,当你调用Future的get()方法以获得结果时,当前线程就开始阻塞,直到call方法结束返回结果。
- Future引用对象指向的实际是FutureTask。
也就是说,总结一句话,Future可以得到别的线程任务方法的返回值。
FutureTask
Future是一个接口,而FutureTask实现了RunnableFuture接口,RunnableFuture继承了Runnable接口和Future接口(继承关系见下图)
FutureTask用于异步获取执行结果或取消执行任务的场景,它的主要功能有:
- 可以判断任务是否完成
- 可以获取任务执行结果
- 可以中断任务
继承关系
常用接口分析
构造函数
FutureTask(Callable<T> callable)
FutureTask(Runnable runnable, T result)
我们来分析一个这两个构造函数的源码
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}
在这里我们可以了解到:
- FutureTask最终都是执行Callable类型的任务。
- 如果构造函数参数是Runnable,会被Executors.callable方法转换为Callable类型。
Callable+FutureTask代码
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newCachedThreadPool();
FutureTask<String> futureTask = new FutureTask<>(new MyCallable());
executorService.submit(futureTask);
executorService.shutdown();
System.out.println("do something in main");
System.out.println("得到异步任务返回结果:" + futureTask.get());
System.out.println("主线程结束");
}
static class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("Callable子线程开始");
Thread.sleep(2000); // 模拟做点事情
System.out.println("Callable子线程结束");
return "MyCallable return";
}
}
运行结果
前面一直讲Callable区别于Runnable原因之一就是可以抛异常,下面代码演示下
还是用上面的例子
Callable+FutureTask抛异常代码
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
FutureTask<String> futureTask = new FutureTask<>(new MyCallable());
executorService.submit(futureTask);
executorService.shutdown();
System.out.println("do something in main");
try {
System.out.println("得到异步任务返回结果:" + futureTask.get());
} catch (Exception e) {
System.out.println("捕获异常成功:" + e.getMessage());
}
System.out.println("主线程结束");
}
static class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("Callable子线程开始");
Thread.sleep(2000); // 模拟做点事情
System.out.println("Callable子线程结束");
throw new NullPointerException("抛异常测试");
}
}
运行结果
总结
- Future和Callable基本是成对出现的,Callable负责产生结果,Future负责获取结果。
- Callable接口类似于Runnable,只是Runnable没有返回值。
- Callable在被线程执行后,可以提供一个返回值,我们可以通过Future的get()方法拿到这个值
- get()方法会导致该线程会被阻塞,直到Future拿到Callable.call()方法的返回值
- Callable的call()方法可以抛出异常,我们可以在尝试执行get()方法时捕获这个异常,而Runnable并不能抛出异常
- FutureTask可以确保任务只执行一次
参考
https://www.jianshu.com/p/cf12d4244171
https://blog.****.net/javazejian/article/details/50896505
https://segmentfault.com/a/1190000012291442
https://juejin.im/post/5a7b2c8b6fb9a0633a70eb54
http://codepub.cn/2016/02/01/Java-multi-thread-Callable-interface-and-thread-pool/
https://www.cnblogs.com/dolphin0520/p/3949310.html