四十二、异步编程之FutureTask

55 阅读7分钟

FutureTask

概述

FutureTask实现了RunnableFuture接口,而RunnableFuture接口继承了Runnable和Future接口。

FutureTask可以作为任务放到线程池中执行。

FutureTask还提供了一系列的方法,用于获取任务的执行结果,判断任务是否执行结束,以及取消任务。

FutureTask应用

板书栏:

public class Test {

    static ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(2);

    public static void main(String[] args) throws InterruptedException {

        FutureTask<String> futureTask = new FutureTask<>(() -> {
            System.out.println("任务开始执行");
            Thread.sleep(1000);
            System.out.println("任务执行结束");
            return "OK";
        });

        executor.execute(futureTask);

        // isDone方法判断任务是否执行结束
//        System.out.println("任务执行结束了吗:" + futureTask.isDone());
//        Thread.sleep(3000);
//        System.out.println("任务执行结束了吗:" + futureTask.isDone());

        // 获取任务的返回结果
//        try {
//            // 一直等待
//            String result = futureTask.get();
//            System.out.println("任务的返回结果:" + result);
//        } catch (ExecutionException e) {
//            e.printStackTrace();
//        }
//
//        try {
//            // 只等待1秒
//            String result = futureTask.get(1000, TimeUnit.SECONDS);
//            System.out.println("任务的返回结果:" + result);
//        } catch (ExecutionException e) {
//            e.printStackTrace();
//        } catch (TimeoutException e) {
//            // 如果等待时间过了,还没有返回结果,返回TimeoutException异常
//            System.out.println("等待超时");
//            e.printStackTrace();
//        }
  
        // futureTask任务是有任务状态的
        // 任务被执行过后,状态发生变化,再次调用run方法任务是不会执行的
        futureTask.run();

        executor.shutdown();
    }
}

要点栏:

要点1:

isDone方法是获取任务是否执行结束的状态

要点2:

无参get方法会一直等待任务结果。有参get方法是有一个时间限制,如果超过等待时间没有返回结果,就会抛出超时异常。

要点3:

FutureTask任务是有任务状态的。任务被执行过一次后,任务状态会发生变化,这个任务就不会再被执行了,除非调用runAndReset方法重置任务状态。

FutureTask源码

FutureTask基本属性

板书栏:

public class FutureTask<V> implements RunnableFuture<V> {

	/**
	 * 任务的几种状态转变过程
	 * NEW -> COMPLETING -> NORMAL         任务正常结束,结果正常返回的过程
     * NEW -> COMPLETING -> EXCEPTIONAL    任务正常结束,结果返回异常的过程
     * NEW -> CANCELLED                    任务被取消
     * NEW -> INTERRUPTING -> INTERRUPTED  任务被中断
	 */
 
	// 存储任务的状态
    private volatile int state;
	// 初始化FutureTask对象时,任务的默认状态
    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;

	// 要执行的任务
    private Callable<V> callable;
 
	// 任务返回的结果
    private Object outcome;
   
	// 执行任务的线程
    private volatile Thread runner;
   
	// 等待任务返回结果的线程Node对象
    private volatile WaitNode waiters;
}

static final class WaitNode {
	volatile Thread thread;
	volatile WaitNode next;
	WaitNode() { thread = Thread.currentThread(); }
}

要点栏:

要点1:

任务是有四种状态变化过程的:

  • 任务正常结束,结果正常返回的过程
  • 任务正常结束,结果返回异常的过程
  • 任务被取消
  • 任务被中断

要点2:

等待返回结果的线程都被封装成了WaitNode对象,并且被挂起。当任务执行结束返回结果时,所有线程都被唤醒。

FutureTask基本方法

run方法

板书栏:

public void run() {
	if (
		// 判断任务状态是否是NEW,如果不是,直接返回不执行
		state != NEW ||
		// 将runner属性修改成当前线程,如果修改失败,直接返回不执行任务
		!UNSAFE.compareAndSwapObject(this, runnerOffset,
									 null, Thread.currentThread()))
		return;
	try {
		// c赋值为任务
		Callable<V> c = callable;
		// 任务不为空的健壮性判断
		// 判断任务状态是否为NEW
		if (c != null && state == NEW) {
			V result;
			boolean ran;
			try {
				// 执行任务
				result = c.call();
				// 任务执行成功,ran赋值为true
				ran = true;
			} catch (Throwable ex) {
				// 任务执行过程中抛出异常
				// result赋值为null
				result = null;
				// ran赋值为false
				ran = false;
				// 封装异常的结果
				setException(ex);
			}
			if (ran)
				// 封装正常的结果
				set(result);
		}
	} finally {
		// 当前线程已经执行完任务
		// runner赋值为null
		runner = null;

		int s = state;
		// 判断任务状态是否大于等于INTERRUPTING
		if (s >= INTERRUPTING)
			// 进入这里说明任务被中断,做相应的处理
			handlePossibleCancellationInterrupt(s);
	}
}
private void handlePossibleCancellationInterrupt(int s) {
	if (s == INTERRUPTING)
		while (state == INTERRUPTING)
			Thread.yield(); 
}

要点栏:

要点1:

如果任务状态不是NEW,或者runner属性无法设置成当前线程,直接返回不执行任务

要点2:

异常的结果会被setException方法处理,正常的结果会被set方法处理

要点3:

如果任务执行过程中,任务被中断,需要做中断处理

set和setException方法

板书栏:

// 任务正常结束封装结果
protected void set(V v) {
	// 将任务状态从NEW改成COMPLETING
	if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
		// 将任务正常结果赋值给outcome
		outcome = v;
		// 将任务状态改成NORMAL
		UNSAFE.putOrderedInt(this, stateOffset, NORMAL); 
		finishCompletion();
	}
}

// 任务异常结束封装结果
protected void setException(Throwable t) {
	// 将任务状态从NEW改成COMPLETING
	if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
		// 将任务异常结果赋值给outcome
		outcome = t;
		// 将任务状态改成EXCEPTIONAL
		UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); 
		finishCompletion();
	}
}

要点栏:

要点1:

set方法是封装正常的任务结果,并且任务状态变化过程是:NEW->COMPLETING->NORMAL

setException是封装异常的任务结果,并且任务状态变化过程是:NEW->COMPLETING->EXCEPTIONAL

要点2:

从COMPLETING状态变成NORMAL或者EXCEPTIONAL状态,中间只有一步操作,将任务执行结果赋值给outcome

cancel方法

板书栏:

public boolean cancel(boolean mayInterruptIfRunning) {
	// 判断任务状态是否是NEW,如果不是,返回false
	// 如果是,基于mayInterruptIfRunning的值修改任务状态
	// mayInterruptIfRunning为true:state从NEW改成INTERRUPTING
	// mayInterruptIfRunning为false:state从NEW改成CANCELLED
	if (!(state == NEW &&
		  UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
			  mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
		return false;
	try {   
		// 判断mayInterruptIfRunning是否为true
		if (mayInterruptIfRunning) {
			try {
				// mayInterruptIfRunning为true
				// 获取执行任务的线程
				Thread t = runner;
				if (t != null)
					// 将执行任务的线程中断标志位设置为true
					t.interrupt();
			} finally {
				// 将任务状态设置为INTERRUPTED
				UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
			}
		}
	} finally {
		finishCompletion();
	}
	return true;
}

要点栏:

要点1:

调用cancel方法,入参为false,任务会从NEW状态变成CANCELLED状态,但是任务会正常执行结束;如果入参为true,任务会从NEW状态变成INTERRUPTING状态,最后变成INTERRUPTED状态。如果任务会响应中断,那么任务执行过程中会抛出异常,不会正常结束。

要点2:

如果任务执行完成后,执行cancel方法,cancel方法返回true

get方法

板书栏:

public V get() throws InterruptedException, ExecutionException {
	int s = state;
	// 判断任务状态是否为NEW或者COMPLETING
	if (s <= COMPLETING)
		// 进入这里说明任务还未执行结束
		// 等待任务结束,被唤醒后返回任务状态
		s = awaitDone(false, 0L);
	// 封装任务结果并返回
	return report(s);
}
private int awaitDone(boolean timed, long nanos)
	throws InterruptedException {
	// 计算出等待的截止时间
	final long deadline = timed ? System.nanoTime() + nanos : 0L;
	// 任务还未结束,线程要被封装成WaitNode
	WaitNode q = null;
	// 任务结束标志
	boolean queued = false;
	for (;;) {
		// 判断线程是否中断
		if (Thread.interrupted()) {
			// 从链表中删除当前线程节点
			removeWaiter(q);
			// 抛出中断异常
			throw new InterruptedException();
		}

		int s = state;
		if (s > COMPLETING) {
			// 进入这里说明任务执行结束
			if (q != null)
				// 将线程的WaitNode对象中thread设置为null
				q.thread = null;
			return s;
		}
		else if (s == COMPLETING)
			// 任务还未结束,线程释放CPU资源等待一会
			Thread.yield();
		else if (q == null)
			// 初始化q
			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);
	}
}
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);
}

要点栏:

要点1:

当任务状态为NEW或者COMPLETING时,get方法会等待任务执行完成;当任务执行结束,线程被唤醒,get方法会返回任务结果

要点2:

挂起的线程都被封装成WaitNode对象,添加到链表中

finishCompletion方法

板书栏:

private void finishCompletion() {
	// q设置成挂起线程中的头节点
	for (WaitNode q; (q = waiters) != null;) {
		// 将waiters属性设置成null
		if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
			for (;;) {
				// t设置成节点的线程
				Thread t = q.thread;
				if (t != null) {
					// 进入这里说明线程不为null
					q.thread = null;
					// 唤醒线程
					LockSupport.unpark(t);
				}
				WaitNode next = q.next;
				if (next == null)
					// 进入这里说明不存在下一个节点
					// 跳出for循环
					break;
				q.next = null; 
				q = next;
			}
			// 所有的线程都已经被唤醒
			break;
		}
	}
	// 预留的方法,等待程序猿实现
	done();
	// 任务执行完成,callable设置为null
	callable = null;  
}

要点栏:

要点1:

finishCompletion方法是在任务执行结束后,唤醒挂起的所有线程,并且将callable属性设置为null