一. 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和waiters。state属性代表了任务的状态,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();
**/
}
}