# 1.Callale和Runnable有哪些不同
# 1.1 Runnable
- Runnable 没有返回值
- 不能跑出checked Exception
Runnable为什么会有这俩个对于日常变成来说很重大的缺陷。
看一下Runnable源码
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
通过代码可以看出,Runnable是一个interface,并且里面只有一个方法,叫做public abstract void run()。这个方法已经规定了run()方法的返回类型是void。而且这个方法没有声明抛出任何异常。所以,当实现并重写这个方法时,我们既不能改返回值类型,也不能更改对于异常抛出的描述,因为在实现方法的时候,语法规定是不允许对这些内容进行修改的。
- Runnable为什么设计成这样 假如run()方法可以返回返回值,或者可以抛出异常,也无济于事,因为我们并没有办法在外层捕获并处理,这是因为调用run()方法的类(比如Thread和线程池)是Java直接提供的,而不是我们编写的。
# 1.2 Callable接口
Callable是一个类似于Runnable的接口,实现Callable接口的类和实现Runnable接口的类都是可以被其他线程执行的任务。我们看一下Callable的源码:
public interface Callable<V> {
V call() throws Exception;
}
可以看出它也是一个interface,并且它的call方法中已经声明throws Exception,前面还有一个V泛型的返回值,这就和之前的Runnable有很大的区别。实现Callable接口,就要实现call方法,这个方法的返回值是范型V,如果把call中计算得到的结果放到这个对象中,就可以利用call方法的返回值来获得子线程的执行结果了。 最后总结一下Callable和Runnable的不同之处:
- 方法名,Callable 规定的执行方法是 call(),而 Runnable 规定的执行方法是 run();
- 返回值,Callable 的任务执行后有返回值,而 Runnable 的任务执行后是没有返回值的;
- 抛出异常,call() 方法可抛出异常,而 run() 方法是不能抛出受检查异常的;
- 和 Callable 配合的有一个 Future 类,通过 Future 可以了解任务执行情况,或者取消任务的执行,还可获取任务执行的结果,这些功能都是 Runnable 做不到的,Callable 的功能要比 Runnable 强大。
# 2.Future的主要功能是什么
# 2.1 Future和Callable的关系
首先我们知道Callable接口是可以有返回值的,那这个返回值如何获取呢。可以用Future类的get方法来获取。因此Future相当于一个存储器,它存储了Callable的call方法的任务结果,同时Future里面也提供了一些方法让我们去使用。
# 2.2 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;
}
# 2.2.1 get()方法:获取结果
get方法最主要的作用就是获取任务执行的结果。该方法执行时的行为取决于Callable任务的状态。可能会发生以下几种情况。
- 当任务已经执行完毕,调用get方法,可以立刻返回,并获取到任务执行的结果。
- 当任务正在执行过程中或者还没有开始执行,调用get方法,都会把当前的线程阻塞,直到任务完成再把结果返回。
- 任务执行过程中抛出异常,调用get方法就会得到抛出的异常
- 任务被取消了。调用get方法去获取结果时则会抛出 CancellationException。
- 任务超时,我们知道 get 方法有一个重载方法,那就是带延迟参数的,调用了这个带延迟参数的 get 方法之后,如果 call 方法在规定时间内正常顺利完成了任务,那么 get 会正常返回;但是如果到达了指定时间依然没有完成任务,get 方法则会抛出 TimeoutException,代表超时了。
来看个简单的Callable和Future使用的代码示例
public static void main(String[] args) {
ExecutorService service=Executors.newFixedThreadPool(10);
Future<Integer> submit = service.submit(new CallableTask());
try {
System.out.println(submit.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
service.shutdown();
}
static class CallableTask implements Callable<Integer>{
@Override
public Integer call() throws Exception {
Thread.sleep(3000);
return new Random().nextInt();
}
}
# 2.2.2 IsDone()方法:判断是否执行完毕
需要注意的是,这个方法如果返回 true 则代表执行完成了;如果返回 false 则代表还没完成。但这里如果返回 true,并不代表这个任务是成功执行的,比如说任务执行到一半抛出了异常。那么在这种情况下,对于这个 isDone 方法而言,它其实也是会返回true。所以isDone方法在返回true的时候,不代表这个任务是执行成功的。只代表执行完毕了。
public static void main(String[] args) {
ExecutorService service=Executors.newFixedThreadPool(10);
Future<Integer> submit = service.submit(new CallableTask());
try {
// 休眠3秒中,为了让任务执行完
Thread.sleep(3000);
System.out.println(submit.isDone());
System.out.println(submit.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
service.shutdown();
}
static class CallableTask implements Callable<Integer>{
@Override
public Integer call() throws Exception {
throw new IllegalArgumentException("Callable抛出异常");
}
}
这段代码证明了三件事情:1.即使任务抛出异常,isDone方法依然会返回true; 2.虽然抛出的异常是 IllegalArgumentException,但是对于 get 而言,它抛出的异常依然是 ExecutionException;
3. 虽然在任务执行一开始时就抛出了异常,但是真正要等到我们执行 get 的时候,才看到了异常。
# 2.2.3 cancel取消任务的执行
- 当任务还没有开始执行,一旦调用cancel,这个任务就会被正常取消,未来也不会被执行,那么 cancel 方法返回 true。
- 如果任务已经完成,或者之前已经被取消过了,那么执行 cancel 方法则代表取消失败,返回 false。因为任务无论是已完成还是已经被取消过了,都不能再被取消了。
- 这个任务正在执行,这个时候执行 cancel 方法是不会直接取消这个任务的,而是会根据我们传入的参数做判断。cancel 方法是必须传入一个参数,该参数叫作mayInterruptIfRunning,它是什么含义呢?如果传入的参数是 true,执行任务的线程就会收到一个中断的信号,正在执行的任务可能会有一些处理中断的逻辑,进而停止,这个比较好理解。如果传入的是 false 则就代表不中断正在运行的任务,也就是说,本次 cancel 不会有任何效果,同时 cancel 方法会返回 false。
# 2.2.4 #### isCancelled() 方法:判断是否被取消
# 3. 用FutureTask来创建Future
除了用线程池的 submit 方法会返回一个 future 对象之外,同样还可以用 FutureTask 来获取 Future 类和任务的结果
代码示例
public static void main(String[] args) {
FutureTask<Integer> task=new FutureTask(new CallableTask());
new Thread(task).start();
try {
System.out.println(task.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
static class CallableTask implements Callable<Integer>{
@Override
public Integer call() throws Exception {
return new Random().nextInt();
}
}
我们来看一下FutureTask的源码
FutureTask继承了RunnableFuture。而RunnableFuture又继承了Runnable和Future。所以FutureTask既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。典型用法是,把Callable实例当作FutureTask构造函数的参数,生成FutureTask的对象,然后把这个对象当作一个Runnable对象,放到线程池中获灵气线程去执行。最后还可以通过 FutureTask 获取任务执行的结果。
# 4. Futrue产生新的线程了吗
有一种说法是,除了继承Thread类和实现Runnable接口之外,还有第三种产生新线程的方式,那就是采用Callable和Future。通过上面的讲解可以很明确的出这种方式是不对的。其实Callable和Future本身并不能产生新的线程,它们需要借助其他的比如Thread类或者线程池才能执行任务。例如上面我们把Callable提交到线程池后,真正执行Callable的其实还是线程池中的线程,而线程池中的线程是由ThreadFactory产生的,这里产生的新线程与Callable、Future都没有关系,所以Future并没有产生新的线程。