九. Executor框架中的任务和任务的执行结果

前言

Executor框架中,提供了Runnable接口和Callable接口用于定义任务,提供了Future接口来表示任务的异步计算结果,本篇文章将对任务和任务的执行结果进行分析。

正文

一. 任务

Executor框架中,Runnable接口和Callable接口用于定义任务,Runnable接口的实现类可以被ThreadThreadPoolExecutorScheduledThreadPoolExecutor执行,Callable接口的实现类可以被ThreadPoolExecutorScheduledThreadPoolExecutor执行,此外,Runnable接口和Callable接口最大的不同在于:Runnable任务没有返回值,Callable任务有返回值。

Executor框架提供的工具类Executors可以将Runnable封装成Callable,方法签名如下所示。

public static Callable<Object> callable(Runnable task)

public static <T> Callable<T> callable(Runnable task, T result)

二. 任务的执行结果

Executor框架提供了Future接口来表示任务的异步计算结果Future接口定义如下所示。

public interface Future<V> {

	// 关闭任务的执行,在任务已经执行完毕或者已经被关闭时,返回false
	// 在该方法调用时如果任务正在被执行,mayInterruptIfRunning决定是否打断执行任务的线程来停止任务
    boolean cancel(boolean mayInterruptIfRunning);

	// 判断任务是否被关闭过
	// 如果在任务执行完之前执行过关闭任务的操作,该方法返回true
    boolean isCancelled();

    // 判断任务是否执行完毕
	// 因为中断,异常和关闭而导致的任务执行完毕,该方法均会返回true
    boolean isDone();

    // 等待任务执行完毕并获取执行结果
    V get() throws InterruptedException, ExecutionException;

    // 在指定时间内等待任务执行完毕并获取执行结果
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

此外,Executor框架中还有一个RunnableFuture接口,该接口继承于Runnable接口和Future接口,即该接口的实现类即可以作为Runnable被执行,也能作为Future获取执行结果。

所以Executor框架提供了一个RunnableFuture接口的实现类FutureTask,而且,在调用ThreadPoolExecutorsubmit()方法提交任务(Runnable或者Callable)或者向ScheduledThreadPoolExecutor提交任务(Runnable或者Callable)时,所提交的任务均会被封装为FutureTask,然后封装成FutureTask的任务会作为Runnable被添加到任务阻塞队列中,同时也会作为Future被返回。

FutureTask的类图如下所示。

在这里插入图片描述

三. FutureTask

FutureTaskExecutor框架中重要的组件,下面对其原理进行学习。FutureTask的关键字段如下所示。

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

    // 任务状态,会有以下几种变化情况:
	// NEW -> COMPLETING -> NORMAL
	// NEW -> COMPLETING -> EXCEPTIONAL
	// NEW -> CANCELLED
	// NEW -> INTERRUPTING -> INTERRUPTED
    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;

    // 需要被执行的任务
    private Callable<V> callable;
    // 任务执行的结果或抛出的异常
    private Object outcome;
    // 正在执行任务的线程
    private volatile Thread runner;
    // 记录调用get()方法等待的线程
    private volatile WaitNode waiters;
	
	......

}

关于FutureTask的状态字段稍后再分析,现在先看一下FutureTask中的需要被执行的任务callable字段,可知该字段为Callable接口,而前面已经分析知道无论是ThreadPoolExecutor还是ScheduledThreadPoolExecutor,均可以将RunnableCallable封装为FutureTask,而FutureTask中却只有Callable,那么肯定是在某个地方将Runnable转换为了Callable,这个地方就是FutureTask的构造函数,如下所示。

public FutureTask(Callable<V> callable) {
	if (callable == null)
		throw new NullPointerException();
	this.callable = callable;
	this.state = NEW;
}

public FutureTask(Runnable runnable, V result) {
	this.callable = Executors.callable(runnable, result);
	this.state = NEW;
}

可以看到,FutureTask的构造函数中,如果传进来的任务为Runnable,那么会使用工具类ExecutorsRunnable转换为Callable。同时构造函数中还会将状态设置为NEW

下面分析FutureTaskrun()方法,源码如下所示。

public void run() {
	// 判断任务状态是否为NEW,状态为NEW的任务才允许被执行
	// 如果任务状态为NEW,则以CAS方式将任务的runner字段设置为当前执行任务的线程,设置成功才允许执行任务
	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) {
				result = null;
				ran = false;
				// 任务执行时抛出异常,将异常赋值给outcome字段
				// 然后以CAS方式将状态先置为COMPLETING,然后置为EXCEPTIONAL,最后唤醒所有调用get()方法进入等待的线程
				setException(ex);
			}
			if (ran)
				// 任务执行时未抛出异常,将执行结果赋值给outcome字段
				// 然后以CAS方式将状态先置为COMPLETING,然后置为NORMAL,最后唤醒所有调用get()方法进入等待的线程
				set(result);
		}
	} finally {
		// 在任务执行结束后将runner字段置为null
		// 在任务执行结束以前runner字段不能为null,以防止任务被并发多次执行
		runner = null;
		int s = state;
		if (s >= INTERRUPTING)
			// 执行到这里,表示任务在执行时(任务状态为NEW)被调用了cancel(true)方法,并且cancel(true)中将任务状态置为了INTERRUPTING或INTERRUPTED
			// 如果任务状态为INTERRUPTING,则循环调用Thread.yield()来放弃时间片,直到任务状态变为INTERRUPTED
			handlePossibleCancellationInterrupt(s);
	}
}

run()方法中,主要的步骤如下。

  • 先在任务状态为NEW的情况下以CAS方式将执行任务的线程runner字段置为当前线程,任务状态不为NEW或者CAS失败,都直接退出run()方法,防止任务被并发多次执行;
  • 任务执行成功或者任务执行抛出异常,会分别以CAS方式将任务状态先置为COMPLETING,如果CAS成功,之后调用cancel()方法会直接返回false,但是如果CAS操作之前先调用了cancel()方法,那么会导致CAS失败,此时任务的状态为INTERRUPTINGCANCELLED或者INTERRUPTED
  • run()方法最后会判断任务的状态是否为INTERRUPTINGINTERRUPTED,如果满足,表明在任务执行完毕并翻转任务状态之前cancel(true)方法被调用了,此时如果任务状态为INTERRUPTING,则循环调用Thread.yield()来放弃时间片以等待任务状态翻转为INTERRUPTED

同时FutureTask还提供了一个runAndReset()方法,该方法与run()方法略有不同,如下所示。

  • runAndReset()不关心任务的执行结果,即不会将任务执行结果赋值给outcome字段;
  • runAndReset()不会主动去翻转任务的状态,即任务正常执行完毕之后状态还是为NEW,以适用于任务需要多次被执行的情况,比如定时任务。

下面分析FutureTaskget()方法,FutureTask提供了一直等待直到任务执行完毕的get()方法,和指定等待时间的get()方法,如下所示。

public V get() throws InterruptedException, ExecutionException {
	int s = state;
	if (s <= COMPLETING)
		s = awaitDone(false, 0L);
	return report(s);
}

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);
}

get()方法中,会先在awaitDone()方法中获取任务状态,如果任务状态为NEW,则进入等待状态。获取到任务状态之后,在report()方法中根据获取到的任务状态来返回任务执行结果。

awaitDone()方法实现如下所示。

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()) {
			// 调用get()方法的线程如果被中断,则将其从等待链表中删除,并抛出中断异常
			removeWaiter(q);
			throw new InterruptedException();
		}

		int s = state;
		if (s > COMPLETING) {
			// 如果任务状态大于COMPLETING,说明任务执行完毕,或抛出异常,或被调用了cancel()方法
			if (q != null)
				q.thread = null;
			// 返回任务的状态
			return s;
		}
		else if (s == COMPLETING)
			// 如果任务状态为COMPLETING,则放弃时间片,目的是为了调用get()方法的线程再次获得时间片时任务状态翻转为NORMAL或者EXCEPTIONAL
			Thread.yield();
		else if (q == null)
			// 执行到这里,说明任务状态为NEW
			// 基于调用get()方法的线程创建一个WaitNode节点
			q = new WaitNode();
		else if (!queued)
			// 如果WaitNode节点没有添加到等待链表,则将其加入到等待链表中
			queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
												 q.next = waiters, q);
		else if (timed) {
			// 如果调用get()方法时指定了等待时间,则使用LockSupport进入等待状态并指定等待时间
			// 如果等待时间到,任务状态还是NEW,则移除WaitNode节点并返回任务状态
			nanos = deadline - System.nanoTime();
			if (nanos <= 0L) {
				removeWaiter(q);
				return state;
			}
			LockSupport.parkNanos(this, nanos);
		}
		else
			// 调用get()方法的线程进入等待状态
			LockSupport.park(this);
	}
}

awaitDone()中,主要步骤如下所示。

  • 如果任务状态大于COMPLETING,即任务执行完毕,或抛出异常,或被调用了cancel()方法,此时返回任务状态;
  • 如果任务状态为COMPLETING,表示任务已经执行完毕(或抛出异常),但是执行结果尚未赋值给outcome字段,此时调用Thread.yield()放弃时间片,因为任务状态从COMPLETINGNORMALEXCEPTIONAL转换的时间非常短,所以Thread.yield()能够让调用get()方法的线程更快的响应任务状态的转换,最终目的是为了让调用get()方法的线程再次获得时间片时任务状态已经翻转为NORMAL或者EXCEPTIONAL
  • 如果任务状态为NEW,那么说明任务还未开始执行或者任务正在执行,此时基于调用get()方法的线程创建一个WaitNode节点并加入等待链表中;
  • 调用get()方法的线程进入等待状态,只有在等待时间到(如果指定了的话),或者任务执行完毕,或者任务执行抛出异常,或者cancel()方法被调用时,所有等待线程才会结束等待状态。

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);
}

下面最后看一下cancel()方法的实现,如下所示。

public boolean cancel(boolean mayInterruptIfRunning) {
	// 如果任务状态不是NEW,则返回false
	// 如果以CAS方式将任务状态置为INTERRUPTING或者CANCELLED失败,则返回false
	// 即任务已经执行完毕或者已经被关闭时,返回false
	if (!(state == NEW &&
		  UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
			  mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
		return false;
	try {
		// mayInterruptIfRunning为true表示需要中断正在执行任务的线程,并最终将任务状态置为INTERRUPTED
		if (mayInterruptIfRunning) {
			try {
				Thread t = runner;
				if (t != null)
					t.interrupt();
			} finally {
				UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
			}
		}
	} finally {
		// 唤醒所有调用get()方法进入等待的线程
		finishCompletion();
	}
	return true;
}

cancel()方法调用时如果任务已经执行完毕(状态为COMPLETINGNORMALEXCEPTIONAL),或者任务已经被关闭(状态为CANCELLEDINTERRUPTINGINTERRUPTED),则直接返回false,并且会根据mayInterruptIfRunning参数来决定是否会中断正在执行任务的线程。

总结

Executor框架中,Runnable接口和Callable接口表示需要被执行的任务,不同在于前者无返回值而后者有返回值。Future接口表示异步计算的结果,同时RunnableFuture接口继承了Runnable接口和Future接口,RunnableFuture接口的实现类FutureTask既能作为Runnable任务来执行,也能作为Future来获取计算结果。

对于RunnableFuture接口的实现类FutureTask,可以通过调用FutureTaskcancel(true)方法来中断正在执行任务的线程,但是请注意,如果线程执行的任务不响应中断,那么这个任务是不会被停止的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

樱花祭的约定

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值