线程池将任务提交和任务执行分离,线程只是一个工作者,它可以执行有返回值的任务,也可以执行没有返回值的任务。接下将详细讨论这些线程执行的任务。
先看下执行任务基本框架:
图1 任务框架
1. Runnable
Runnable是一个没有返回值的任务,看下该接口的定义:
public interface Runnable {
//线程执行该任务时将会运行该方法
public abstract void run();
}
这个接口很简单,下面代码片断是一个简单的用法:
Thread worker = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("I'm running");
}
});
worker.start();
创建一个线程并为该线程指定一个任务,启动线程后该任务就会被执行。
2. Callable
Callable是一个有返回值的任务,看下该接口的定义:
public interface Callable<V> {
//线程执行任务时将会运行该方法
V call() throws Exception;
}
Callable的call方法可以抛出异常(包括运行时和检查异常),而Runnable的run方法不会抛出检查异常。
另外,注意到Callable是一个泛型接口,call方法的返回值是一个泛型。
3. Future
Future接口代表一个异步执行结果,它提供了检查任务是否完成的方法。在取任务执行结果时,如果任务未完成,取结果的线程会阻塞,直到任务完成并返回。
Future提供了取消任务的方法,如果执行没有返回值(例如Runnable)的任务,并且希望能够取消任务,可以定义Future
- public interface Future<V> {
- //尝试取消正在执行的任务,如果任务已经完成该操作就会失败。如果任务还未执行取消就会成功。
- //如果任务正在执行,方法的参数就会指示线程是否需要中断,从而尝试停止任务的执行。
- boolean cancel(boolean mayInterruptIfRunning);
- //任务完成之前是否已经被取消
- boolean isCancelled();
- //任务是否已经完成
- //任务正常终止、抛出异常、或者被取消,该方法都会返回true
- boolean isDone();
- //取任务执行的结果,如果任务未完成,取结果的线程会阻塞。
- //如果任务被取消了,该方法抛出CancellationException
- //如果线程被中断,抛出中断异常
- V get() throws InterruptedException, ExecutionException;
- //支持超时时间的get方法
- //可能抛出超时异常
- V get(long timeout, TimeUnit unit)
- throws InterruptedException, ExecutionException, TimeoutException;
- }
4. RunnableFuture
RunnableFuture继承了Runnable和Future接口。可以这样理解,RunnableFuture是一个具有异步执行结果的任务,并且可以通过Future的方法取该任务的执行结果。看下RunnableFuture接口的定义:
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}
5. FutureTask
FutureTask是一个可取消的异步任务,实现了RunnableFuture接口。
FutureTask所代表的任务执行有7种状态,分别由7个静态私有变量表示,我们先看下这7个状态:
- //任务执行的最新状态,在下面7个状态中取值
- private volatile int state;
- //新建状态,任务都从该状态开始
- private static final int NEW = 0;
- //正在执行任务的状态
- private static final int COMPLETING = 1;
- //任务正常执行完成的状态
- private static final int NORMAL = 2;
- //任务执行过程中抛出了异常
- private static final int EXCEPTIONAL = 3;
- //任务被取消(不响应中断)
- private static final int CANCELLED = 4;
- //任务正在被中断
- private static final int INTERRUPTING = 5;
- //任务已经中断
- private static final int INTERRUPTED = 6;
任务一共有7种状态,但是同一个任务只会经历4种状态转移序列
NEW -> COMPLETING -> NORMAL
NEW -> COMPLETING -> EXCEPTIONAL
NEW -> CANCELLED
NEW -> INTERRUPTING -> INTERRUPTED
任务总会从NEW状态开始,一个状态转移序列可以为:
从任务新建状态、到任务正在执行状态、到任务正常结束的状态,这是一个任务正常结束的状态转移序列。
从任务新建状态、到任务正在执行状态、到任务执行过程中抛出异常转到了异常状态。FutureTask的任务执行是会捕获异常的(运行时和检查异常都会捕获),捕获到异常后将任务状态标识为异常状态。
从任务新建状态到任务被取消状态,只有在线程不可中断的时候调用cancel方法,任务才可能经历该状态转移序列。
从任务新建状态、到任务正在被中断状态、到任务已经中断状态,只有在线程可中断的时候调用cancel方法,任务才可能经历该状态转移序列。
FutureTask可以保证已经完成的任务不会被再次运行或者被取消。
再看下FutureTask其他的属性:
//需要执行的任务,任务执行结束后将会被设置为null
private Callable<V> callable;
//调用get方法取任务执行的返回结果,get方法也有可能抛出异常
private Object outcome;
//执行该任务的线程,任务执行完成之后设置为null
private volatile Thread runner;
//通过链表保存等待获取结果的线程
private volatile WaitNode waiters;
下面我们详细介绍FutureTask的方法,创建一个FutureTask后,通常会提交给一个线程池运行,线程将会执行FutureTask的run方法,先看下run方法:
- public void run() {
- //1. 如果该任务不是新建状态,说明该任务已经被调度执行过了,直接返回,否则
- //2. 如果已经有线程将要执行该任务,直接返回,否则
- //3. 设置执行该任务的线程为当前线程
- if (state != NEW ||
- !UNSAFE.compareAndSwapObject(this, runnerOffset,
- null, Thread.currentThread()))
- return;
- try {
- Callable<V> c = callable;
- //只有任务(Callable)不为空并且任务状态为NEW,才会去执行该任务
- if (c != null && state == NEW) {
- V result;
- boolean ran;
- try {
- //执行任务
- result = c.call();
- //到这,说明任务已经执行完成了
- ran = true;
- } catch (Throwable ex) {
- //捕获任务执行过程中抛出的异常,设置任务状态为EXCEPTIONAL并将任务返回结果outcome设置为异常ex
- result = null;
- ran = false;
- setException(ex);
- }
- //如果任务正常完成,设置任务状态为NORMAL并将任务返回结果outcome设置为result
- if (ran)
- set(result);
- }
- } finally {
- //不管任务执行结果如何,最终都将执行线程runner设置为null,防止该任务再次被执行
- runner = null;
- //再读一次,避免丢失中断,为什么呢?
- //如果runner线程已经结束,但是其它线程调用了runner的interrupted方法,这时runner是感知不到其他
- //线程中断过自己,因为自己已经结束了。如果runner不等待一会,就可能会丢失中断消息
- int s = state;
- if (s >= INTERRUPTING)
- //处理可能的中断(cancel(true)),如果当前任务正在中断过程中,runner自旋等待一会,直到中断完成。
- //注意,这里的中断是由别的线程中断当前runner的,如果当前runner发现其他线程正在中断自己,
- //该runner就会让出CPU时间,回到线程可执行态,直到中断完成
- handlePossibleCancellationInterrupt(s);
- }
- }
任务执行过程中如果抛出了异常,调用setException设置任务的返回值为该异常:
- protected void setException(Throwable t) {
- //原子的将任务状态从NEW转移到COMPLETING
- //这种状态转移序列对应了状态变化:NEW -> COMPLETING -> EXCEPTIONAL
- if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
- //设置任务执行结果为异常t
- outcome = t;
- //任务最终状态为EXCEPTIONAL
- UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL);
- finishCompletion();
- }
- }
任务正常执行结束后,调用set方法设置任务的返回值:
- protected void set(V v) {
- //这种状态转移序列对应了状态变化:NEW -> COMPLETING -> NORMAL
- if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
- outcome = v;
- //任务最终状态为NORMAL
- UNSAFE.putOrderedInt(this, stateOffset, NORMAL);
- finishCompletion();
- }
- }
FutureTask任务提交给线程池执行后,我们通过get方法获取该任务的执行结果,看下get方法的实现:
- public V get() throws InterruptedException, ExecutionException {
- int s = state;
- //状态为NEW或者COMPLETING,说明任务没有完成,等待任务执行完成
- if (s <= COMPLETING)
- s = awaitDone(false, 0L);
- //返回任务执行结果,可能会抛出异常
- return report(s);
- }
如果当前任务状态处于NEW或者COMPLETING的状态,需要等待任务执行完成。
学习awaitDone方法之前 ,我们先看下FutureTask的内部类WaitNode:
- static final class WaitNode {
- //保存等待获取结果的线程
- volatile Thread thread;
- //下一个等待节点
- volatile WaitNode next;
- WaitNode() { thread = Thread.currentThread(); }
- }
FutureTask的私有变量waiters就是WaitNode类型的,waiters用于保存等待获取任务执行结果的线程,如果有多个线程都调用get方法并且任务并没有执行完成,就需要将这些线程保存起来,等任务执行完成后唤醒这些线程。WaitNode的实现其实就是一个简单的单向链表。
看下awaitDone方法:
- //timed:是否超时等待
- //nanos:timed=true时有效,等待的时间,单位是纳秒
- private int awaitDone(boolean timed, long nanos)
- throws InterruptedException {
- //到期时间
- final long deadline = timed ? System.nanoTime() + nanos : 0L;
- WaitNode q = null;
- boolean queued = false;
- for (;;) {
- //如果当前线程被中断,从等待链表中移除该节点,并抛出异常
- if (Thread.interrupted()) {
- removeWaiter(q);
- throw new InterruptedException();
- }
- int s = state;
- if (s > COMPLETING) {
- if (q != null)
- q.thread = null;
- //如果任务已经执行完成,返回任务执行状态
- return s;
- }
- //任务正在执行,让出CPU时间
- else if (s == COMPLETING)
- Thread.yield();
- else if (q == null)
- //当前线程创建一个等待节点
- q = new WaitNode();
- else if (!queued)
- //如果节点未入队,将节点入队并将新节点设置为链表的第一个节点
- queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
- q.next = waiters, q);
- else if (timed) {
- //超时等待
- nanos = deadline - System.nanoTime();
- if (nanos <= 0L) {
- removeWaiter(q);
- return state;
- }
- LockSupport.parkNanos(this, nanos);
- }
- else
- //挂起当前线程
- LockSupport.park(this);
- }
- }
awaitDown方法的逻辑就是将当前取结果的线程放到WaitNode的等待队列中,当任务有结果了,awaitDown也就会返回,否则就将取结果的线程挂起。
awaitDown方法返回后,get方法调用report方法,返回任务执行状态,看下report方法的实现:
- private V report(int s) throws ExecutionException {
- Object x = outcome;
- if (s == NORMAL)
- return (V)x;
- if (s >= CANCELLED)
- throw new CancellationException();
- throw new ExecutionException((Throwable)x);
- }
report方法返回任务正常处理后的返回值,也可能抛出异常。
当任务处理状态为NORMAL时,说明任务正常处理完成,返回outcome。
当任务处理状态为CANCELLED、INTERRUPTING或者INTERRUPTED时,说明任务被取消了,抛出CancellationException。
当任务处理状态为EXCEPTIONAL时,说明任务执行过程中抛出异常了,此时outcome应是异常的信息,转换成Throwable后继续抛出。FutureTask的任务在线程池中被执行的时候,如果抛出了异常,get结果的时候会得到该异常。
FutureTask任务是可取消的,这也是它一个重要特性,看下cancel方法:
//参数mayInterruptIfRunning标识任务执行过程中是否可中断
- public boolean cancel(boolean mayInterruptIfRunning) {
- //只有任务状态为NEW才可被取消,否则说明已经完成、取消或者中断了
- if (state != NEW)
- return false;
- if (mayInterruptIfRunning) {
- //可以被中断,将任务状态设置为INTERRUPTING
- if (!UNSAFE.compareAndSwapInt(this, stateOffset, NEW, INTERRUPTING))
- return false;
- Thread t = runner;
- if (t != null)
- //中断正在执行该任务的线程
- t.interrupt();
- //设置任务最终状态为INTERRUPTED
- //此任务状态转移序列对应了:NEW -> INTERRUPTING -> INTERRUPTED
- UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
- }
- //任务不可被中断,将任务的状态设置为CANCELLED
- //此任务状态转移序列对应了:NEW -> CANCELLED
- else if (!UNSAFE.compareAndSwapInt(this, stateOffset, NEW, CANCELLED))
- return false;
- finishCompletion();
- return true;
- }
cancel方法尝试取消任务的执行,如果参数为false,任务将会经历NEW -> CANCELLED状态序列。如果参数为true,任务将会经历NEW -> INTERRUPTING -> INTERRUPTED状态序列。
如果一个任务正在run,此时执行cancel方法,如果该任务不响应中断,那么不会影响任务的执行,只会影响任务的状态。如果任务执行时发现状态不是NEW,可能是被cancel了,任务就不会被执行。
我们发现,有很多方法最后都调用了finishCompletion,顾名思义,该方法是任务结束后的收尾方法,看下该方法的实现:
//唤醒等待队列中的线程并从等待队列移除
- private void finishCompletion() {
- //自旋,直到waiters为空
- for (WaitNode q; (q = waiters) != null;) {
- //尝试将waiters设置为null
- if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
- //唤醒等待队列中所有的节点线程
- for (;;) {
- Thread t = q.thread;
- if (t != null) {
- q.thread = null;
- //唤醒该节点线程
- LockSupport.unpark(t);
- }
- WaitNode next = q.next;
- if (next == null)
- break;
- q.next = null;
- q = next;
- }
- break;
- }
- }
- //FutureTask中这是一个空方法,留给子类去实现
- done();
- //任务设置为null
- callable = null;
- }
finishCompletion方法唤醒等待队列waiters中所有节点线程并删除节点。
最后看下FutureTask两个构造函数:
- //一个参数的构造函数,指定了callable
- public FutureTask(Callable<V> callable) {
- if (callable == null)
- throw new NullPointerException();
- this.callable = callable;
- //任务的初始状态都是NEW
- this.state = NEW;
- }
//两个参数的构造函数,指定了callable,并且callable的返回值指定为result
- public FutureTask(Runnable runnable, V result) {
- //Executors.callable方法只是包装了一个callable,指定了callable的返回值为result
- this.callable = Executors.callable(runnable, result);
- this.state = NEW;
- }
好了,FutureTask介绍到此为止。