Runnable封装了一个异步运行的任务,只有一个没有参数和返回值的run方法,同时也不能抛出异常;Callable与其类似,只有一个call方法,但是它有参数和返回值,还能抛出异常,Callable可以看作是Runnable的加强版。
Runnable和Callable的定义如下:
从中可以看到:
1、 Callable能接受一个泛型,然后在call方法中返回一个这个类型的值,例如Callable<Integer>
表示一个最终返回Integer对象的异步计算。而Runnable的run方法没有返回值。
2、 Callable的call方法可以抛出异常,而Runnable的run方法不会抛出异常。
适合用Callable的情况
1、 需要线程返回结果
当一个线程需要另外一个比较耗时的线程的运算结果的时候,如果我们用Runnable实现线程的话,就需要使用共享变量和线程间的通信来完成,同时还需要等待线程执行完毕,可见就相对复杂且效率低。
2、 需要线程抛出异常
我们在使用Runnable执行一些可能会抛出异常的任务的时候,都只能在run方法内部进行异常的捕获,而不能抛给上一级调用者进行异常的处理,这也造成了极大的不便。
由于Runnable的局限性,在以上两种情况的时候,使用Callable都拥有较高的效率。
Future
Callable线程允许返回值,由于线程是异步执行的,所以我们就需要一个数据结构来保存异步任务的结果,这个数据结构就是Future。
Future表示一个可能还没有完成的异步任务的结果,针对这个结果,可以添加Callback以便在任务执行成功或失败后作出相应的操作。
Future接口提供了五个方法:
它提供了检查计算是否完成的方法,以等待计算的完成,并获取计算的结果。计算完成后只能使用 get 方法来获取结果,如有必要,计算完成前可以阻塞此方法。取消则由 cancel 方法来执行。还提供了其他方法,以确定任务是正常完成还是被取消了。一旦计算完成,就不能再取消计算。如果为了可取消性而使用 Future 但又不提供可用的结果,则可以声明 Future<?> 形式类型、并返回 null 作为底层任务的结果。
Future的核心思想是:一个线程的执行任务可能非常耗时,等待该线程返回,显然不明智。所以可以在启动线程的时候就返回一个Future,可以通过Future去控制任务的执行。
Future的继承和实现关系如下:
其中最常用的就是FutureTask。
FutureTask
FutureTask实现了RunnableFuture接口,而RunnableFuture同时继承了Future和Runnable。
所以,FutureTask除了实现Future接口外,还实现了Runnable接口。因此,FutureTask可以交给Executor执行,也可以由调用线程直接执行,并且可以直接返回其运算结果。它既是一个Runnable又是一个Future。
FutureTask的状态
根据FutureTask中run方法被执行的时机,FutureTask处于不同的状态,在FutureTask中定义了如下几种状态:
一个FutureTask被new出来,即处于NEW状态,COMPLETING用的是进行时态,表示正在执行,NORMAL代表顺利完成,EXCEPTIONAL表示执行过程中出现异常,CANCELLED代表执行过程被取消,INTERRUPTING表示正在被中断,即已经接受到了中断的指令,在完成中断前的最后工作,而INTERRUPTED表示已经被中断了。
一个FutureTask可能的状态迁移过程:
1、 NEW -> COMPLETING -> NORMAL:正常结束
2、 NEW -> COMPLETING -> EXCEPTIONAL:异常结束
3、 NEW -> CANCELLED:取消而结束
4、 NEW -> INTERRUPTING -> INTERRUPTED:中断而结束
FutureTask的get和cancel的执行
当FutureTask处于未启动或已启动状态时,执行FutureTask.get()方法将导致调用线程阻塞;当FutureTask处于已完成状态时,执行FutureTask.get()方法将导致调用线程立即返回结果或抛出异常。
当FutureTask处于未启动状态时,执行FutureTask.cancel()方法将导致此任务永远不会被执行;
当FutureTask处于已启动状态时,执行FutureTask.cancel(true)方法将以中断执行此任务线程的方式来试图停止任务;
当FutureTask处于已启动状态时,执行FutureTask.cancel(false)方法将不会对正在执行此任务的线程产生影响(让正在执行的任务运行完成);
当FutureTask处于已完成状态时,执行FutureTask.cancel()将返回false。
如下图:
Callable+FutureTask使用
FutureTask包装机制与Callable搭配使用非常便利,它将可以使Callable转换成Runnable和Future,由new Thread(FutureTask).start()启动,并可调用FutureTask.get()获取计算结果。
例如:
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class CallableTest {
public static void main(String[] args) {
//1 创建一个Callable线程
Callable<Integer> myCallable = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
// TODO Auto-generated method stub
int num = 0;
for (int i = 1;i <= 100;i++ ) {
num = num + i;
}
return num;
}
};
//2 FutureTask包装Callable
FutureTask<Integer> myTask = new FutureTask<>(myCallable);
//3 启动线程
new Thread(myTask).start();
//4 获取结果
try {
Integer result = myTask.get();
System.out.println(result);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
如果FutureTask被中断了,get()方法将会抛出InterruptedException,如果FutureTask出现了异常,则get()方法会抛出ExecutionException异常。
FutureTask中run方法
FutureTask实现了Runnable接口,所以它也实现了run方法,当通过Thread启动的时候,将会执行run方法中的逻辑。
其run方法的源码如下:
首先是判断当前FutureTask的状态,如果当前状态并不支持线程启动则直接return返回,即当FutureTask的状态state不为NEW,或者runner不为空(即已经有线程在执行);如果runner为空,则将runner的值更新为当前线程。
如果当前状态允许执行,则创建一个Callable对象来接收被包装的Callable对象,如果该Callable对象不为空且处于NEW状态,则创建result变量来保存直接调用call方法计算的结果,布尔类型的变量ran表示是否成功执行。
如果成功调用call方法,则将result设置为FutureTask的放回结果。如果call方法抛出异常则result置为null,ran置为false。最后finally语句块中将负责启动的Thread置为null,并判断是否被中断。
注:compareAndSwap方法是Java并发包中的一种CAS操作,java.util.concurrent中大量使用了CAS操作,涉及到并发的地方都调用了sun.misc.Unsafe类方法进行CAS操作。这个方法有四个参数,其中第一个参数为需要改变的对象,第二个为偏移量(即之前求出来的valueOffset的值),第三个参数为期待的值,第四个为更新后的值。整个方法的作用即为若调用该方法时,value的值与expect这个值相等,那么则将value修改为update这个值,并返回一个true,如果调用该方法时,value的值与expect这个值不相等,那么不做任何操作,并范围一个false。
FutureTask包装Runnable
其实,FutureTask不仅可以包装Callable,还可以包装Runnable,只不过是先将Runnable通过Excutors包装成Callable。