目录
前言
通常一个请求分为请求-处理-返回,如果通过异步线程去完成一个任务,我们通常会选择FutureTask +Submit+ Callable()来实现获取线程的返回值。
FutureTask继承了Future和Runnable,Future代表了线程的生命周期的状态机,而Runnable则通过这个状态去获取处理的结果。
1、应用实例
问题思考:
如何让一个线程有返回值?
应用代码示例:通过FutureTask + Callable来实现线程的返回值;
public class FutureTaskTest implements Callable<String> {
@Override
public String call() throws Exception {
return "hello callable";
}
public static void main(String[] args) throws Exception {
FutureTaskTest futureTaskTest = new FutureTaskTest();
FutureTask<String> futureTask = new FutureTask<>(futureTaskTest);
new Thread(futureTask).start(); //线程一:线程池或者创建线程;
System.out.println(futureTask.get()); //线程二:阻塞获取结果
//线程池的支持
ExecutorService executors = new ThreadPoolExecutor(5, 10, 20, TimeUnit.MINUTES,
new LinkedBlockingQueue<Runnable>(1000));
//submit不会抛异常,除非调用future.get(); execute()会抛出
Future<String> future = executors.submit(futureTaskTest);
System.out.println(future.get());
}
}
2、Future
Future表示一个任务线程的生命周期,通过线程状态机(完成-异常-正在执行等)来控制线程,提供相应的方法来判断是否已经完成或取消,以及获取任务的结果和取消任务等,方法比较简单。

具体线程功能控制函数如下:
public interface Future<V> {
// 发送取消命令给线程并返回是否发送成功;
boolean cancel(boolean mayInterruptIfRunning);
// 当前的Future是否被取消,返回true表示已取消
boolean isCancelled();
// 当前Future是否已结束。包括运行完成、抛出异常以及取消,都表示当前Future已结束;
boolean isDone();
// 获取Future的结果值。如果当前Future还没有结束,那么当前线程就等待,直到Future运行结束,那么会唤醒等待结果值的线程的。
V get() throws InterruptedException, ExecutionException;
// 获取Future的结果值。与get()相比较多了允许设置超时时间
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
3、FutureTask-实现原理
FutureTask的类图:

3.1、生产消费者模型
从class类图可以看出,FutureTask是Runnable和Future的结合。我们可以把Runnable比作是生产者,Future比作是消费者,生产者运行任务run()方法计算结果,消费者通过get()方法获取结果。 作为生产者消费者模式,有一个很重要的机制,就是如果生产者数据还没准备的时候,消费者会被阻塞;当生产者数据准备好了以后会唤醒消费者继续执行。
-
生产者:Runnable 生产计算结果;
-
消费者:future- 线程的生命周期,通过 线程的状态机来判断线程是否执行结束;从而异步获取结果;
-
futureTask:将Future和Runnable两者结合起来 。
这里结果的获取get()涉及到2个线程的协作:
-
第一个线程:执行Callable()的任务:执行callable业务方法call方法,返回结果值result;
-
第二个线程:通过 回调函数get()方法来获取 线程的执行状态state来异步获取结果;
-
如果没有执行完毕:则被- awaitDone() - park()阻塞加入到阻塞链表中等待线程执行;
-
如果执行完毕: finishCompletion(),唤醒阻塞的线程获取结果;
-
-
单链表实现阻塞线程的存储;
-
生产消费模式通过Supportlock实现线程的阻塞park()和唤醒unpark();
3.2、state的含义
表示FutureTask当前的状态,分为七种状态:
private volatile int state;
// NEW 新建状态,表示这个FutureTask还没有开始运行
private static final int NEW = 0;
// COMPLETING 完成状态, 表示FutureTask任务已经计算完毕了但是还有一些后续操作,例如唤醒等待线程操作,还没有完成;
private static final int COMPLETING = 1;
// FutureTask任务完结,正常完成,没有发生异常;
private static final int NORMAL = 2;
// FutureTask任务完结,因为发生异常;
private static final int EXCEPTIONAL = 3;
// FutureTask任务完结,因为取消任务;
private static final int CANCELLED = 4;
// FutureTask任务完结,也是取消任务,不过发起了中断运行任务线程的中断请求;
private static final int INTERRUPTING = 5;
// FutureTask任务完结,也是取消任务,已经完成了中断运行任务线程的中断请求;
private static final int INTERRUPTED = 6;
4、源码分析
线程一:run()方法调用call方法执行任务task;
4.1、run()方法
从类图发现FutureTask实现了Runnable(),所以一定有一个run()方法,看看run方法里做了什么?
其实run()方法作用非常简单,就是调用callable的call方法返回结果值result,根据是否发生异常,调用 set(result)或 setException(ex)方法表示 FutureTask 任务完结。不过因为FutureTask任务都是在多线程环境中使用,所以要注意并发冲突问题。
注意在run()方法中,我们没有使用Synchronized代码块或者Lock来解决并发问题,而是使用了CAS这个乐观锁来实现并发安全,保证只有一个线程能运行FutureTask任务.
public class FutureTask<V> implements RunnableFuture<V> {
public interface RunnableFuture<V> extends Runnable, Future<V> {
/**
* Sets this Future to the result of its computation
* unless it has been cancelled.
*/
void run();
}
public void run() {
if (state != NEW || //线程安全保证:使用cas来保证只有一个线程能运行task任务,而不是使用synchronized或者lock
!UNSAFE.compareAndSwapObject(this, runnerOffset, null, Thread.currentThread()))
return;
try {
Callable<V> c = callable; //传进来的任务实例对象 c不为null且状态为新建的状态;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
result = c.call(); //调用任务实例的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);
}
}
当线程执行结束以后,唤醒等待结果的Runnable线程,通过set设置返回结果:
protected void set(V v) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = v;
UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
finishCompletion(); // 执行完成,通知获取结果的线程获取结果
}
}
当执行任务的线程执行结束,然后唤醒等待结果的get线程获取结果:
private void finishCompletion() {
// assert state > COMPLETING;
for (WaitNode q; (q = waiters) != null;) {
if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
for (;;) {
Thread t = q.thread;
if (t != null) {
q.thread = null;
LockSupport.unpark(t); //唤醒获取结果的阻塞的线程
}
WaitNode next = q.next;
if (next == null)
break;
q.next = null; // unlink to help gc
q = next;
}
break;
}
}
done();
callable = null; // to reduce footprint
}
4.2、get()方法
阻塞获取结果的get函数,如果发现执行任务的线程没有结束,则直接进入阻塞等待状态。get()的主要作用:
-
线程未执行结束的时候,状态小于complete,调用awaitDone方法,其实也是调用 park() 阻塞获取结果的线程,将该线程插入到 单链表等待队列;
-
run()方法执行完set值的时候通过调用unpark()唤醒获取结果的线程,获取结果或者抛出异常;
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L); //如果线程还没有执行完,就会被阻塞;
return report(s);
}
如果当前的结果还没有被执行完,把当前线程线程和插入到等待队列,阻塞取结果线程,直至任务线程执行结束时被唤醒unpark():
private int awaitDone(boolean timed, long nanos)
throws InterruptedException {
final long deadline = timed ? System.nanoTime() + nanos : 0L;
WaitNode q = null; //wait节点,构建一条单链表记录等待的线程
boolean queued = false;
for (;;) {
if (Thread.interrupted()) {
removeWaiter(q);
throw new InterruptedException();
}// 当状态大于COMPLETING时,表示FutureTask任务已结束。
else if (s == COMPLETING) // cannot time out yet
Thread.yield();
// 将当前阻塞线程插入到等待线程链表中;
else if (q == null)
q = new WaitNode();
else if (!queued)//使用CAS函数将新节点添加到链表中;
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); //没有设置超时时间的阻塞;
}
}
//等待节点结构
static final class WaitNode {
volatile Thread thread;
volatile WaitNode next; //单向链表
WaitNode() { thread = Thread.currentThread(); } //保存当前的阻塞的线程
}
4.3、report()
report方法就是根据传入的状态值 s,来决定是抛出异常,还是返回结果值,这个两种情况都 表示 FutureTask 完结了。
private V report(int s) throws ExecutionException {
Object x = outcome; //表示call的返回值;
if (s == NORMAL) //表示正常完结状态;
return (V)x;
if (s >= CANCELLED)// 大于或等于CANCELLED,都表示手动取消FutureTask任务,所以抛出CancellationException异常;
throw new CancellationException();
// 否则就是运行过程中,发生了异常,这里就抛出异常
throw new ExecutionException((Throwable)x);
}
到这里,通过futureTask来获取线程的返回值的流程就分析完了,因为线程是异步执行,要想获取它的结果,必须的有另外一个线程来监视它的执行过程,然后才能获取执行结果。
5、submit和execute的区别
回头再来看看线程池执行任务的两种方法:submit 和 execute,两者有什么区别呢?
5.1、execute
-
1、execute 只能接受一个Runnable任务;
-
2、execute如果 出现异常会直接抛出;
-
3、execute 没有返回值;
public interface Executor {
/**
* Executes the given command at some time in the future. The command
* may execute in a new thread, in a pooled thread, or in the calling
* thread, at the discretion of the {@code Executor} implementation.
*
* @param command the runnable task
* @throws RejectedExecutionException if this task cannot be
* accepted for execution
* @throws NullPointerException if command is null
*/
void execute(Runnable command);
}
5.2、submit
-
1、submit可以接受Runnable 和Callable这两种类型的参数;
-
2、对于submit方法,如果传入一个Callable,可以得到一个Future的返回值; Callable + Future
-
3、submit方法调用 不会抛异常,除非调用 Future.get ();
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
// 将task封装了一个RunnableFuture
RunnableFuture<T> ftask = newTaskFor(task);
//交由线程池区处理ftask任务;
execute(ftask);
return ftask;
}
结论:通过源码可以看出,调用的submit方法,这里其实相对于execute方法来说,只多做了一步操作,就是 封装了一个RunnableFuture,然后调用execute方法,这里就是调用线程池里的worker线程来调用过ftask的run方法,执行任务,而这个ftask其实就是FutureTask里面最终实现的任务逻辑。
6、使用注意事项
-
异步阻塞获取结果需要加上超时时间,否则一致阻塞;
-
单个请求单个try-catch,否则所有的请求都会当成异常来处理;
-
超时后,结果返回null,但要注意服务端可能是完成了;
-
带返回值的submit (CallableTask),如果不调用future.get()是不会抛出异常的;
-
不带返回值的excute(Runnable)直接抛异常;
-
@Async注解,当没有返回值的时候,需要自定义异常实现捕获异常,有返回值调用future.get()OK;
7、小结
为了实现数据的安全性和一致性,有时需要返回线程的执行结果,这里就用到了FutureTask来实现线程的返回值,但是需要注意阻塞和异常的处理,需要设置超时时间和异常必须调用get()捕获。
OK---我自横刀向天笑,去留肝胆两昆仑。
水滴石穿,积少成多。学习笔记,内容简单,用于复习,梳理巩固。