Callable, Future, FutureTask

一. Callable  vs  Runnable

几乎每个Java学习者都知道,如何新建线程?

1. 直接继承Thread类

2. 实现Runnable接口

其实理解很简单,Thread相当于工人(工作主体),Runnable相当于工人工作搬的砖(工作内容)。

只使用Runnable有很大的局限性:开发者想要知道run( )方法的调用结果,只能在run( )里面加上日志打印,或者放入某个共享的数据结构描述工作内容,它不能够返回一个值或抛出一个受检查的异常。而Java 1.5以来引入的Callable接口则很好解决了这个问题。

我们对比下Runnable和Callable的java源代码:

public interface Runnable{
	void run();
}

可以看到,run( )返回类型为void。Callable位于java.util.concurrent包下,它是一个泛型接口。call( )函数的返回类型为泛型类型V,要使用Callable来表示无返回值的任务,则表示为Callable<Void>。

public interface Callable<V>{
	V call() throws Exception;
}

Runnable多用于新建线程指定工作任务,也可用于线程池 (ExecutorService) 来执行工作任务;Callable则在线程池中有较多应用场景。


二. Future

不太同于传统新建线程即销毁的过程,线程池中的Executor生命周期通常分为4阶段:创建、提交、开始、完成

如何表示一个任务的生命周期呢?因为传统情况中,用户无法知道线程将任务执行到什么阶段了。Future即登场,Future提供相应调用方法给出是否已经取消,是否已经完成。任务的生命周期只能前进,不能后退(某个任务完成后,永远停留在“完成”状态上)。

Future接口的主要java源代码为:

public interface Future<V>{
	boolean cancel(boolean mayInterruptIfRunning);
	boolean isCancelled();
	boolean isDone();
	V get() throws InterruptedException, ExecutionException, CancellationException;
	V get(long timeout, TimeUnit unit)throws InterruptedException, ExecutionException,
	                  CancellationException, TimeoutException;
}

cancel( )用于取消任务,取消任务成功返回true,否则返回false。局部参数mayInterruptIfRunning表示是否允许取消正在执行却没有完成的任务。

isCancelled( )用于想外界提供是否任务被取消成功

isDone( )用于表示任务是否完成。 

get( )用于获取执行结果,返回值通过state值来判断该返回执行结果、阻塞还是抛出异常。Future<V>仍然只是个接口,其具体实现为FutureTask<V>类。


三. FutureTask

FutureTask要达成两个目的:Future来表示任务执行状态, Runnable表示具体任务内容

为了明显同时达成这两个目的,java源代码抽象出RunnableFuture扩展接口:

public interface RunnableFuture<V> extends Runnable, Future<V>{ }

然后FutureTask实现RunnbaleFuture接口同时达到这两个目的。达到这两个目的,先来看看FutureTask缓存的四个比较重要的值:

private volatile int state;                //当前任务状态,需要线程安全volatile
private Callable<V> callable;              //任务主体内容
private Object outcome;                    //get()的实际返回对象
private volatile Thread runner;            //当前运行线程currentThread
private volatile WaitNode waiters;         //维系多个任务执行顺序的工作栈

接下来从任务状态、任务运行、任务结果获取、任务取消来分析这个比较复杂的实现类,分别讲讲这几个域缓存了什么。

 

任务状态

FutureTask类的state表征当前任务状态。其所有可能取值通过int枚举:

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;       //任务被取消(中断取消)

于是,就存在4种状态变化(注意,中间态非常短暂,通常可认为是瞬时的):

            

唯一例外的是带参数的get(long timeout, TimeUnit unit),指定时间内要是未获得结果则抛出TimeoutException,返回null。

 

任务运行

我们知道要是FutureTask顺利运行肯定能返回预期结果,但运行不顺利就会抛出异常并取消。任务运行的主体是run函数:

public void 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) {
                result = null;
                ran = false;
                setException(ex);
            }
            if (ran)
                set(result);
        }
    } finally {
        runner = null;
        int s = state;
        if (s >= INTERRUPTING)
            handlePossibleCancellationInterrupt(s);
    }
}

我们看到run方法内,有两点发现:

1. Callable值其实是result,根据情况不同分为set和setException。

2. 不管发生情况,finally语句块一定要执行释放当前任务的工作线程,初始化state。

接下来看到set和setException的方法实现:

	protected void set(V v) {
		if(UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
			outcome = v;
			UNSAFE.putOrderedInt(this, stateOffset, NORMAL);   //CAS设置state为NORMAL
			finishCompletion();           //清空waiters对应的任务栈
		}
	}
	
	protected void setException(Throwable t) {
		if(UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
			outcome = t;
			UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL);
			finishCompletion();
		}
	}

其实这中间主要内容就两个:设置返回值,清空任务栈

 

任务结果获取

任务运行阶段缓存了任务结果对象outcome,那么get其实就是向外界提供outcome访问。但是由于调用者会在各种时间节点调用outcome,所以需要区分任务状态。

那么先看FutureTask是如何实现Future接口的无参数get( )和有参数的get调用:

	@Override
	public V get() throws InterruptedException, ExecutionException {
		// TODO Auto-generated method stub
		int s = state;
		if(s <= COMPLETING)
			s = awatiDone(false, 0L);
		return report(s);
	}

	@Override
	public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
		// TODO Auto-generated method stub
		if(null == unit)
			throw new NullPointerException;
		int s = state;
		if(s <= COMPLETING && (s = awaitDone(true, unit.toNanos(timeout))) <= COMPLETING)
			throw new TimeoutException();
		return report(s);
	}

可以看到,get( )方法的核心调用是report( )函数和awaitDone( )函数。先来看report( )函数:

	private Object outcome;                    //get()的实际返回对象
	
	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方法会check当前state的状态,

NORMAL:任务执行完成,当前outcome不为null。可以将结果数据转换后返回(outcome不为null前提是先发生任务运行);

EXCEPTIONAL:任务执行中抛出异常,包装成ExecutionException返回;

CANCELLED, INTERRUPTING, INTERRUPTED:任务被取消,均抛出CancellationException;

再来看awaitDone方法的源代码,这一块逻辑相对有些复杂:

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

	private static final sun.misc.Unsafe UNSAFE;
	private static final long stateOffset;
	private static final long runnerOffset;
	private static final long waiterOffset;
	static {
		try {
			UNSAFE = sun.misc.Unsafe.getUnsafe();
			Class<?> k = Future.class;
			stateOffset = UNSAFE.objectFieldOffset(k.getDeclaredField("state"));
			runnerOffset = UNSAFE.objectFieldOffset(k.getDeclaredField("runner"));
			waiterOffset = UNSAFE.objectFieldOffset("waiters");
		}catch(Exception e) {
			throw new Error(e);
		}
	}
	
	private void removeWaiter(WaitNode node) {
		if(null != node) {
			node.thread = null;
			retry:
				for(;;) {
					for(WaitNode pred = null, q = waiters, s; q != null; q = s) {
						s = q.next;
						if(null != q.thread)
							pred = q;
						else if(null != pred) {
							s = pred.next;
							if(null == pred.thread) 
								continue retry;
						}
						else if(!UNSAFE.compareAndSwapObject(this, waiterOffset, q, s))
							continue retry;
					}
					break;
				}
		}
	}
	
	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;
			}
			else if(s == COMPLETING) {
				Thread.yield();
			}
			else if(q == null)
				q = new WaitNode();
			else if(!queued)
				queued = UNSAFE.compareAndSwapObject(this, waiterOffset, 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);
		}
	}

FutureTask即维护了一个工作线程构成的单向链表(为什么不用数组?因为线程经常变化,更注重插入/删除效率),waiters为这个链表的头指针(一般都愿意称为栈,waiters则为指向栈顶的指针)。

UNSAFE的静态代码块主要对应CAS操作的3个属性:state、runner和waitersstate属性代表了任务的状态,waiters属性代表了指向栈顶节点的指针,runner属性代表了执行FutureTask中的“Task”(实体为Runnable)的线程。定义这3种属性的目的,是为了弄清楚具体要取消哪个线程,它目前的状态是什么,取消后是否会对其他线程造成影响。

CAS操作核心是compareAndSwapxxx (xxx表示这可以是Object, Integer等任何封装类型),这是一种乐观锁机制,这里不作展开。

removeWaiter的操作就是移除局部参数中的WaitNode,通常是任务已经没有对应执行线程了。这就分成两种情况来讨论:

(1) 要移除的WaitNode在栈顶

该node位于栈顶,那么此时头节点q也就是node:q. thread == null,前驱节点pred == null。直接进入最后分支:

else if (!UNSAFE.compareAndSwapObject(this, waitersOffset, q, s))
    continue retry;

这里采用了CAS比较,原栈顶元素出栈后,将栈顶元素设置成原节点的下一个节点。

需要注意,retry语句块不管CAS操作是否成功都会执行:

CAS操作不成功:程序会回到retry处重来;

CAS操作成功:程序继续遍历完整个链表,找寻node.thread == null 的节点一并剔除;

(2) 要移除的WaitNode不在栈顶

当要移除的node不在栈顶时,程序一直遍历整个链表,直到找到q.thread == null的节点并进入

else if (pred != null) {
    pred.next = s;
    if (pred.thread == null) // check for race
        continue retry;
}

因为当前node不在栈顶,则其必然是有前驱节点pred的。链表删除一个元素即让前驱node指向被删除node的下一个节点,从而达到剔除当前节点的目的。

后面的if判断则有点耐人寻味。因为removeWaiter是允许多个线程同时执行的,很可能线程A在执行当前node出栈操作时,前驱节点pred被线程B标记需要出栈。这样岂不是我们还要找寻pred.pred并指定其指向下一个节点?递归是个很吃资源的操作,所以这里的逻辑是:如果这种情况发生,线程A完成后继续执行retry语句块,从头开始遍历链表;如果pred节点没有被标记,继续往后遍历完整个链表。

awaitDone()逻辑则清晰许多,它主要是加互斥锁阻塞至有state返回(注意返回值是任务状态,而非任务结果)。

 

任务取消

Future接口是带了Cancel抽象方法的,所以FutureTask也就有必要实现任务取消这个方法。

public boolean cancel(boolean mayInterruptIfRunning) {
    if (!(state == NEW && UNSAFE.compareAndSwapInt(this, stateOffset, NEW, mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
        return false;
    try {   
        if (mayInterruptIfRunning) {
            try {
                Thread t = runner;
                if (t != null)
                    t.interrupt();
            } finally {
                UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
            }
        }
    } finally {
        finishCompletion();
    }
    return true;
}

之前我在Future接口中提到Cancel()方法的局部参数mayInterruptIfRunning表示是否允许取消正在执行却没有完成的任务。因而就分为这两种情况:

case 1.  mayInterruptIfRunning = false: 任务状态state对应NEW -> CANCELLED;

case 2. mayInterruptIfRunning = true:任务状态state对应于NEW -> INTERRUPTING -> INTERRUPTED

实际上,我不知道case 1算不算java埋的一个雷,因为case 1只是简单的把state状态设为CANCELLED。线程仍有可能去执行任务,只是任务即使执行完毕了,也无法设置任务的执行结果。因为run方法对state状态为NEW为前提的,所以这种情况仍有可能发生。

case 2则不同,无论如何当前执行任务的线程都会被中断,至于Callable响不响应中断不作要求。因为这个主要目的是为了停止任务执行,节省CPU资源。

关于FutureTask这一块,某位大神分析得比我详细多了,详见https://segmentfault.com/a/1190000016572591


四. FutureTask使用

实际使用方式主要分为:Callable+Future,Callable+FutureTask,线程资源使用方式都使用线程池(倒不是说不能new Thread, 只是不够优化)。线程池提供了多个重载submit方法来满足不同的工作内容,我们最常用涉及的为以下两个:

<T> Future<T> submit(Callable<T> task);
Future<?> submit(Runnable task);

如果采用输入局参为Callable,我们先写出工作主体内容TaskCall

class TaskCall implements Callable<Integer>{
	@Override
	public Integer call() throws Exception{
		int sum = 0;
		for(int i=0; i<100; i++) {
			sum+=i;
		}
		return sum;
	}
}

用Future来实现分配工作内容给线程:

public class FutureDemo {
	
	public static void main(String[] args) {
		ExecutorService executor = Executors.newCachedThreadPool();
		Taskcall taskcall = new Taskcall();
		Future<Integer> result = executor.submit(taskcall);
		executor.shutdown();
	}
		
}

如果用FutureTask来实现,用FutureTask来包装工作主体内容 (FutureTask可是实现了Runnable接口的,其构造函数内会把Runnable包装成Callable)然后再提交:

public class FutureDemo {
	
	public static void main(String[] args) {
		ExecutorService executor = Executors.newCachedThreadPool();
		TaskCall taskcall = new TaskCall();	
		FutureTask<Integer> resultTask = new FutureTask<Integer>(taskcall);
		executor.submit(resultTask);
		executor.shutdown();

	   /* 当然你也可以不用线程池,但这样坏处是每次都要新建线程
		Thread work = new Thread(resultTask);
		work.start();
		**/
	}
		
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值