FutureTask除了实现Future接口外,还实现了Runnable接口。因此其除了能交给Executor执行,也可以由调用线程直接执行(FutureTask.run())。以下是个人的经验总结,也参考了网上不少的文章,参考详见文末列表。
实现原理总结
关于实现原理,建议先看深入学习FutureTask文中的Why章节。
FutureTask类内部维护一个volatile int 类型的字段state
,表示任务的执行状态。
其中
COMPLETING
状态和INTERRUPTING
状态是保持时间很短的中间状态。NORMAL、EXCEPTIONAL、CANCELLED、INTERRUPTED是最终状态。
另外在FutureTask的run
方法中有以下代码片段:
public void run() {
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
//以下代码略
}
UNSAFE.compareAndSwapObject(this, runnerOffset, null, Thread.currentThread()))
方法用于原子性设置变量对象字段值。this表示当前FutureTask实例,runnerOffset表示要修改的字段(runner)偏移量,null表示未修改时的期望值,Thread.currentThread()表示修改后的目标值。看以下代码片段:
/** The thread running the callable; CASed during run() */
private volatile Thread runner;//表示执行Callable的线程
private static final long runnerOffset;
static {
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class<?> k = FutureTask.class;
runnerOffset = UNSAFE.objectFieldOffset
(k.getDeclaredField("runner"));
}catch(Execption){
}
}
另外,参考文中也提到
发起任务线程跟执行任务线程通常情况下都不会是同一个线程,在任务执行线程执行任务的时候,任务发起线程可以查看任务执行状态、获取任务执行结果、取消任务等等操作。
这正符合了Executor框架中对任务的创建与分离的基本思想。这样做,既能充分利用多核CPU带来的执行工作效率上的提升,也能更好的解耦工作便于理解和维护。
常见使用方式
参考1中已经提到常见的三种使用方式,这里主要指出一些特别之处。
/**
* 第一种方式:Future + ExecutorService
* Task task = new Task();
* ExecutorService service = Executors.newCachedThreadPool();
* Future<Integer> future = service.submit(task1);
* service.shutdown();
*/
/**
* 第二种方式: FutureTask + ExecutorService
* ExecutorService executor = Executors.newCachedThreadPool();
* Task task = new Task();
* FutureTask<Integer> futureTask = new FutureTask<Integer>(task);
* executor.submit(futureTask);
* executor.shutdown();
第一种方式得到的异步结果对象future
与第二种futureTask
在本质上都是FutureTask
类型,截止到JDK1.8
<T> Future<T> submit(Callable<T> task);
Future<?> submit(Runnable task);
<T> Future<T> submit(Runnable task, T result);
这些方法的返回对象类型都是FutureTask
。
网友评论