FutureTask源码导读

498 阅读8分钟

FutureTask是在JDK1.5被引入的,又是并发大神Doug Lea的杰作。 它代表的是一个异步计算任务,它将在未来的某个时刻完成计算并获得一个计算结果。当线程调用get()去获取结果时,如果计算完毕,则立马返回,否则线程会被阻塞,直到计算完成。

应用场景例如:你需要导出一份Excel,但不是马上要下载,此时你可以创建一个FutureTask实例,并提交到线程池中运行,当用户想要下载时,通过FutureTask获取到Excel的生成路径去找到文件并响应给客户端。

UML 在这里插入图片描述 FutureTask实现了Runnable接口,代表它可以交给线程去执行,同时又实现了Future接口,代表它是一个异步任务,在未来的某个时间可以获得一个结果,而且任务是可以取消的。

源码导读

FutureTask没有依赖AQS,相对比较完整和独立。 它有一个内部类WaitNode,代表阻塞线程的节点,这些节点构成了一个简单的单向链表。 当线程调用get()获取结果时,如果计算还没有结束,FutureTask则会调用awaitDone()将当前线程入对并Park。

属性

callable变量用来保存带有返回值的计算任务。 outcome用来保存计算结果,如果计算出异常了,则保存的是异常信息。 runner用来记录执行计算的线程。 waiters是一个单向链表,记录的是等待计算结果的阻塞线程。

// 带返回值的任务,运行结束后会被清空
private Callable<V> callable;
// 计算的结果,如果计算过程中异常了,则保存异常信息。
private Object outcome;
// 执行计算任务的线程
private volatile Thread runner;
// 等待队列,单向链表
private volatile WaitNode waiters;

state用来表示FutureTask的状态,它有七种值,全都用静态常量声明了:

// 任务的状态
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;//线程已经被中断

构造函数

FutureTask提供了两个构造函数:

  1. 传入Callable,任务开始调用它的call()获得计算结果。
  2. 传入Runnable和结果result,run()执行结束后,返回result,这种属于事先就定义好结果了。
/*
给定一个Callable,将在run()中执行它,并保留结果
 */
public FutureTask(Callable<V> callable) {
	if (callable == null)
		throw new NullPointerException();
	this.callable = callable;
	this.state = NEW;       // ensure visibility of callable
}

/*
给定一个Runnable和结果result。
将在run()中执行它,并在执行结束后返回result。
 */
public FutureTask(Runnable runnable, V result) {
	this.callable = Executors.callable(runnable, result);
	this.state = NEW;       // ensure visibility of callable
}

核心方法

get

get()可以获取计算结果,如果任务没有计算完毕,线程会阻塞。 FutureTask提供了两种get()方法,都是响应中断的:

  1. get() 获取结果,否则无期限等待,直到计算完成,或线程中断。
  2. get(long timeout, TimeUnit unit) 支持超时的获取结果,直到计算完成、超时、或线程中断。

逻辑是一样的,判断状态,如果是NEWCOMPLETING则需要调用awaitDone()让线程等待一会儿,否则说明任务计算完成或者失败了,不需要阻塞,返回结果即可。

/*
获取计算结果,这个过程是阻塞的,需要等待计算完成。
 */
public V get() throws InterruptedException, ExecutionException {
	int s = state;
	if (s <= COMPLETING)
		/*
		两种状态下需要等待:
		1.NEW:任务新建好,还在计算过程中。
		2.COMPLETING:计算好了,但是还没保存计算结果,这时不会入队Park,
		而是Thread.yield(),暂时让出CPU的执行权,等待保存计算结果。
		 */
		s = awaitDone(false, 0L);
	// 计算完成,或异常,取消了都直接返回,不等待。
	return report(s);
}

/*
支持超时的获取计算结果,LockSupport.parkNanos()会指定线程挂起的时间。
 */
public V get(long timeout, TimeUnit unit)
		throws InterruptedException, ExecutionException, TimeoutException {
	if (unit == null)
		throw new NullPointerException();
	int s = state;
	if (s <= COMPLETING &&
			(s = awaitDone(true, unit.toNanos(timeout))) <= COMPLETING)
		throw new TimeoutException();
	return report(s);
}

先看awaitDone()等待的逻辑:

  1. 判断线程是否中断,是则抛异常,并移除节点,因为get()是响应中断的。
  2. 判断state是否大于COMPLETING,是则说明任务计算完成或失败,已经不需要等待了。
  3. 判断state是否等于COMPLETING,是则说明计算已经完成,只是还没有保存结果而已,马上就快好了,此时线程调用Thread.yield()暂时让出CPU的执行权,过会儿再来看结果。
  4. 如果还没计算完毕,则创建WaitNode入队并Park。
/*
等待计算完成:
何时终止?
	1.计算完成了。
	2.超时了。
	3.被中断了。
 */
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) {
			/*
			状态大于COMPLETING就不用阻塞了,可以返回了:
			1.计算完成了。
			2.任务被取消了。
			3.计算过程抛异常了。
			4.计算任务被中断了。
			 */
			if (q != null)
				q.thread = null;
			return s;
		}
		else if (s == COMPLETING)
			/*
			计算完成还不能返回,需要等待计算结果保存到outcome,
			并将state设置为:NORMAL。
			此时可以让出CPU的执行权。
			 */
			Thread.yield();
		else if (q == null)
			// 创建节点,准备入队
			q = new WaitNode();
		else if (!queued)
			/*
			CAS+自旋的方式,头插法入队。
			为什么采用头插法?
			1.没有用单独的属性来记录尾巴,如果采用尾插法,每次都需要从链头查找到链尾,效率很低。
			2.并发的时候,可能存在节点全部唤醒完毕,但是新建节点才刚刚入队,无法被唤醒的问题。
			 */
			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
			// 成功入队,挂起当前线程,等待run()计算完成后唤醒
			LockSupport.park(this);
	}
}

等待超时、线程被中断都需要移除节点:

/*
当任务被中断,或超时,需要移除节点。
并不会只移除传进来的node节点,而是对整个链表检查一遍,thread=null的节点都要移除。
 */
private void removeWaiter(WaitNode node) {
	if (node != null) {
		node.thread = null;
		retry:
		for (;;) {          // restart on removeWaiter race
			for (WaitNode pred = null, q = waiters, s; q != null; q = s) {
				s = q.next;
				if (q.thread != null)
					pred = q;
				else if (pred != null) {
					pred.next = s;
					/*
					移除的过程中,前面又有新的节点需要移除,则跳出循环重新检查一遍。
					后面的节点无所谓,因为本身就是从前向后扫描的。
					 */
					if (pred.thread == null)
						continue retry;
				}
				else if (!UNSAFE.compareAndSwapObject(this, waitersOffset,
						q, s))
					continue retry;
			}
			break;
		}
	}
}

如果计算完成了,则通过report()返回计算结果,它有三种情况:

  1. 计算正常完成,返回结果。
  2. 任务被取消了,抛CancellationException
  3. 计算任务本身异常了,抛ExecutionException
// 报告任务计算的结果,或者抛出异常。
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);
}

run

run()方法不应该显式的调用,而是交给线程池去调用。 线程池调用run(),其实就是执行Callable,获取返回值,并唤醒等待线程。

// 执行任务
public void run() {
	/*
	开始计算有两个前提:
	1.state为NEW新建状态。
	2.任务的执行线程为null,通过CAS的方式修改为当前线程。
	确保多线程并发执行run(),也只有一个线程可以计算任务。
	 */
	if (state != NEW ||
			!UNSAFE.compareAndSwapObject(this, runnerOffset,
					null, Thread.currentThread()))
		return;
	try {
		Callable<V> c = callable;
		if (c != null && state == NEW) {
			V result;
			boolean ran;//成功完成计算的标记
			try {
				result = c.call();
				ran = true;
			} catch (Throwable ex) {
				// 计算过程发生异常,结果为null,ran为false
				result = null;
				ran = false;
				setException(ex);//保存异常信息,并通知节点
			}
			if (ran)
				// 如果计算完成,保存计算结果,并通知节点
				set(result);
		}
	} finally {
		// runner must be non-null until state is settled to
		// prevent concurrent calls to run()
		runner = null;
		// state must be re-read after nulling runner to prevent
		// leaked interrupts
		int s = state;
		if (s >= INTERRUPTING)
			// 任务被中断了,自旋调用Thread.yield(),暂时让出CPU的执行权,等待state设为INTERRUPTED。
			handlePossibleCancellationInterrupt(s);
	}
}

计算有两种结果:异常和正常。

计算正常:set(V v)

// 保存计算结果
protected void set(V v) {
	if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
		// 保存计算结果
		outcome = v;
		UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
		// 唤醒阻塞节点
		finishCompletion();
	}
}

计算异常:setException(Throwable t)

/*
任务计算过程中异常了,保存异常信息,调用get()时返回给客户端。
 */
protected void setException(Throwable t) {
	if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
		// 保存异常信息
		outcome = t;
		UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
		// 计算异常了,也要唤醒阻塞的节点。
		finishCompletion();
	}
}

不论是正常还是异常,都需要唤醒所有等待线程。

/*
任务结束,唤醒阻塞节点
 */
private void finishCompletion() {
	// assert state > COMPLETING;
	// 遍历单向链表
	for (WaitNode q; (q = 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; // unlink to help gc
				q = next;
			}
			break;
		}
	}

	// 钩子函数,完成后触发的动作,默认不做任何事情,由子类扩展
	done();

	callable = null;        // to reduce footprint
}

FutureTask在任务结束时,调用了done(),这是一个钩子函数,默认什么都不做,是交给子类去扩展的。

cancel

FutureTask的任务是可以取消的: mayInterruptIfRunning是否中断任务的执行,如果为true则会打断计算任务,如果为false,任务会继续执行,只是不再关心它的结果了。 任务只有在NEW状态下才能被取消。 任务取消后,会唤醒所有的等待线程,告诉他们别再等了。

/*
取消任务:
mayInterruptIfRunning:是否中断任务的执行?
	true:中断工作线程,任务停止。(只是修改线程的interrupt标记,是否响应中断需要自己实现。)
	false:任务取消后,工作线程继续执行。
 */
public boolean cancel(boolean mayInterruptIfRunning) {
	/*
	state为NEW状态,且CAS改为INTERRUPTING/CANCELLED成功才返回true。
	 */
	if (!(state == NEW &&
			UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
					mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
		// 任务已经计算完成,或被取消,计算异常,被中断都算取消失败。
		return false;
	try {    // 防止调用interrupt()抛异常,捕获一下
		if (mayInterruptIfRunning) {
			// 如果需要中断,则将工作线程interrupt()
			try {
				Thread t = runner;
				if (t != null)
					t.interrupt();
			} finally {
				// 将状态改为已中断
				UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
			}
		}
	} finally {
		// 任务取消后,需要唤醒阻塞节点
		finishCompletion();
	}
	return true;
}

其他方法

isDone() 任务是否完成:

/*
任务是都完成/结束:不是新建状态都算.
 */
public boolean isDone() {
	return state != NEW;
}

isCancelled() 任务有没有被取消:

/*
任务有没有被取消:主动取消,计算异常,被中断都算任务取消。
 */
public boolean isCancelled() {
	return state >= CANCELLED;
}

总结

FutureTask的源码不算太难,阅读相对比较轻松。 它用一个int类型的state变量来标记任务的状态,默认是NEW,在计算完成之前一直都是NEW,也只有这个状态下的任务才能被取消。 如果还没计算完毕,线程调用get()会被阻塞,FutureTask会创建一个和线程绑定的WaitNode节点,并采用头插法的方式添加到链表的头部,并将其Park。 COMPLETING代表任务已经计算完毕,只是还没保存结果,马上就快好了,这个状态下线程调用get()是不会被Park的,而是Thread.yield()暂时让出CPU的执行权,过会儿再来看看。 之后要么是计算成功完成,获得计算结果,要么是异常,要么被中断了,不论如何任务都算是结束了,都需要唤醒所有的等待线程。