FutureTask是怎样获取到异步执行结果的?
所谓异步任务,就是不在当前线程中进行执行,而是另外起一个线程让其执行。那么当前线程如果想拿到其执行结果,该怎么办呢?
如果我们使用一个公共变量作为结果容器,两个线程共用这个值,那么应该是可以拿到结果的,但是这样一来,对业务就会造成侵入干扰了,因为你始终得考虑将这个共享变量传入到这个异步线程中去且要维持其安全性。
我们知道,Future.get() 可以获取异步执行的结果,那么它是怎么做到的呢?
要实现线程的数据交换,我们按照进程间的通信方式可知有: 管道、共享内存、Socket套接字。而同一个jvm的两个线程通信,所有线程共享内存区域,则一定是通过共享内存再简单不过了。
本文将以 ThreadPoolExecutor 线程池 来解释这个过程。
首先,如果想要获取一个线程的执行结果,需要调用 ThreadPoolExecutor.submit(Callable); 方法。然后该方法会返回一个 Future 对象,通过 Future.get(); 即可获取结果了。
它具体是怎么实现的呢?
一、首先,我们来看一下 submit 过程
仅为返回了一个 Future<?> 的对象供下游调用!
// AbstractExecutorService
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
// 包装一层结果,RunnableFuture, 也实现了 Runnable 接口
// 实际上就是 FutureTask
RunnableFuture<T> ftask = newTaskFor(task);
// 然后交由 线程池进行调用任务了,即由 jvm 调用执行 Thread
// 具体执行逻辑,在我之前的文章中也已经阐述,自行搜索
execute(ftask);
// 最后,把包装对象返回即可
return ftask;
} /**
* Returns a {@code RunnableFuture} for the given callable task.
*
* @param callable the callable task being wrapped
* @param <T> the type of the callable's result
* @return a {@code RunnableFuture} which, when run, will call the
* underlying callable and which, as a {@code Future}, will yield
* the callable's result as its result and provide for
* cancellation of the underlying task
* @since 1.6
*/
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
return new FutureTask<T>(callable);
} // FutureTask 实例化
/**
* Creates a {@code FutureTask} that will, upon running, execute the
* given {@code Callable}.
*
* @param callable the callable task
* @throws NullPointerException if the callable is null
*/
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
二、异步线程如何执行?
通过上面的分析,我们可以看到,异步线程的执行被包装成了 FutureTask, 而java的异步线程执行都是由jvm调用Thread.run()进行, 所以异步起点也应该从这里去找:
// 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 {
// 直接调用 call() 方法,获取返回结果
result = c.call();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
// 执行异常,包装异常信息
setException(ex);
}
// 将结果设置到当前的 FutureTask 实例变量 outcome 中,这样当前线程就可以获取了
// 设置结果时,会将 state 同时变更
if (ran)
set(result);
}
} finally {
// runner must be non-null until state is settled to
// prevent concurrent calls to run()
runner = null;
// state must be re-read after nulling runner to prevent
// leaked interrupts
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
} /**
* Sets the result of this future to the given value unless
* this future has already been set or has been cancelled.
*
* <p>This method is invoked internally by the {@link #run} method
* upon successful completion of the computation.
*
* @param v the value
*/
protected void set(V v) {
// 设置结果时,还不代表可以直接获取了,还有后续工作,所以设置为 COMPLETING 中间态
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = v;
UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
// 通知线程执行完成等后续工作
finishCompletion();
}
} /**
* Removes and signals all waiting threads, invokes done(), and
* nulls out callable.
*/
private void finishCompletion() {
// assert state > COMPLETING;
// 外部看起来是一个 for, 实际上只会执行一次, 目的是为了保证内部的锁获取成功
// 如果有其他线程成功后, waiters也就会为null, 从而自身也一起退出了
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;
// 只有把所有 wait 线程都通知完后,才可以退出
if (next == null)
break;
q.next = null; // unlink to help gc
q = next;
}
break;
}
} // 完成后钩子方法,默认为空,如果需要做特殊操作可以自行复写即可
done(); callable = null; // to reduce footprint
}
// 简单看一下异常信息的包装,与 正常结束方法类似,只是将 outcome 设置为了异常信息,完成状态设置为 EXCEPTIONAL
/**
* Causes this future to report an {@link ExecutionException}
* with the given throwable as its cause, unless this future has
* already been set or has been cancelled.
*
* <p>This method is invoked internally by the {@link #run} method
* upon failure of the computation.
*
* @param t the cause of failure
*/
protected void setException(Throwable t) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = t;
UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
finishCompletion();
}
}
在上面这些实现中,我们也会有点迷糊,我干啥来了?
不管怎么样,你明白一点,所有的执行结果都被放到 FutureTask 的 outcome 变量中了,我们如果想要知道结果,那么,只需要获取这个变量就可以了。
当然,也不可能这么简单了,起码你得知道什么时候获取该变量是合适的才行!接下来!
三、如何获取异步执行结果?
当然是用户调用 future.get() 获取了!
// Future.get()
public V get() throws InterruptedException, ExecutionException {
int s = state;
// 只要状态值小于 COMPLETING, 就说明任务还未完成, 去等待完成
if (s <= COMPLETING)
s = awaitDone(false, 0L);
// 只要等待完成, 再去把结果取回即可
return report(s);
}
// 我们先看一下结果的取回逻辑 report(), 果然不出意外的简单, 只管取 outcome 即可
/**
* Returns result or throws exception for completed task.
*
* @param s completed state value
*/
@SuppressWarnings("unchecked")
private V report(int s) throws ExecutionException {
Object x = outcome;
// 正常执行完成, 直接返回
if (s == NORMAL)
return (V)x;
// 此处会包含 CANCELLED/INTERRUPTING/INTERRUPTED
if (s >= CANCELLED)
throw new CancellationException();
// 业务异常则会被包装成 ExecutionException
throw new ExecutionException((Throwable)x);
}
// 看到取结果这么简单,那么 等待结束的逻辑的呢?看起来好像没那么简单了
/**
* Awaits completion or aborts on interrupt or timeout.
*
* @param timed true if use timed waits
* @param nanos time to wait, if timed
* @return state upon completion
*/
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()) {
// 因 q 是链表的头,所以会移除所有的等待队列,即中断是对所有线程的
removeWaiter(q);
throw new InterruptedException();
} int s = state;
// 执行完成后,将线程置空即可,删除工作会有其他地方完成
if (s > COMPLETING) {
if (q != null)
q.thread = null;
return s;
}
// 正在处理结果,稍作等待即可
else if (s == COMPLETING) // cannot time out yet
Thread.yield();
// 其他情况,先创建自己的等待标识,以便在下一次循环中进行入队等待
else if (q == null)
q = new WaitNode();
// 进行一次入队等待,将 q 作为头节点
else if (!queued)
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);
}
}
可以看到,等待逻辑还是有点多的,毕竟场景多。至此,我们已经完全看到了一个,如何获取异步线程的执行结果实现了。总结下:
1. 实现Runnable接口,由jvm进行线程调用;
2. 包装 Callable.call()方法,带返回值,当线程被调起时,转给 call() 方法执行,并返回结果;
3. 将结果封装到当前future实例中,以备查;
4. 当用户调用get()方法时,保证状态完成情况下,最快速地返回结果;
四、扩展: Future.get() vs Thread.join()
Future.get()方法,一方面是为了获取异步线程的执行结果,另一方面也做到了等待线程执行完成的效果。
而 Thread.join() 则纯粹是为了等待异步线程执行完成,那它们有什么异曲同工之妙吗?来看下
// Thread.join(), 通过 isAlive() 判断是否完成
/**
* Waits for this thread to die.
*
* <p> An invocation of this method behaves in exactly the same
* way as the invocation
*
* <blockquote>
* {@linkplain #join(long) join}{@code (0)}
* </blockquote>
*
* @throws InterruptedException
* if any thread has interrupted the current thread. The
* <i>interrupted status</i> of the current thread is
* cleared when this exception is thrown.
*/
public final void join() throws InterruptedException {
join(0);
} /**
* Waits at most {@code millis} milliseconds for this thread to
* die. A timeout of {@code 0} means to wait forever.
*
* <p> This implementation uses a loop of {@code this.wait} calls
* conditioned on {@code this.isAlive}. As a thread terminates the
* {@code this.notifyAll} method is invoked. It is recommended that
* applications not use {@code wait}, {@code notify}, or
* {@code notifyAll} on {@code Thread} instances.
*
* @param millis
* the time to wait in milliseconds
*
* @throws IllegalArgumentException
* if the value of {@code millis} is negative
*
* @throws InterruptedException
* if any thread has interrupted the current thread. The
* <i>interrupted status</i> of the current thread is
* cleared when this exception is thrown.
*/
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0; if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
} // 无限期等待
if (millis == 0) {
while (isAlive()) {
// 这是个 native 方法,即由jvm进行控制
// Thread 任务执行完成后,将进行 notifyAll()
// 同理下面的限时等待
wait(0);
}
} else {
// 限时等待
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
可以看到, Thread.join() 的等待逻辑是依赖于 jvm 的调度的, 通过 wait/notify 机制实现。与 Future.get() 相比,它是在 之后的,且无法获取结果。
五、Runnable如何包装成Callable ?
Callable 其实就只是实现了一个 call() 方法而已,如果我们只实现了 Runnable, 是否就拿不到返回值呢?并不是,我们可以直接指定返回值对象或者不指定,使用Runnable进行submit();
// 不指定返回值的 Runnable, 此处的返回值一定 void
public Future<?> submit(Runnable task);
// 指定返回值的 Runnable, 由 T 进行返回值接收
public <T> Future<T> submit(Runnable task, T result);
但是 Runnable 是怎么变成 Callable 的呢?其实就是一个 适配器模式的应用,我们来看一下!
// AbstractExecutorService.submit()
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
// 明确返回值为 Void
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
// 同样使用 FutureTask 进行封装,只是调用了不同的构造器
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
return new FutureTask<T>(runnable, value);
}
// FutureTask, 使用 Executors 工具类生成一个 callable, 屏蔽掉 Callable 与 Runnable 的差异
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}
// Executors 使用一个适合器类将 Runnable 封装成 Callable
/**
* Returns a {@link Callable} object that, when
* called, runs the given task and returns the given result. This
* can be useful when applying methods requiring a
* {@code Callable} to an otherwise resultless action.
* @param task the task to run
* @param result the result to return
* @param <T> the type of the result
* @return a callable object
* @throws NullPointerException if task null
*/
public static <T> Callable<T> callable(Runnable task, T result) {
if (task == null)
throw new NullPointerException();
return new RunnableAdapter<T>(task, result);
}
// 而 RunnableAdapter 也是很简单, 仅将 call() 转而调用 run() 方法即可
/**
* A callable that runs given task and returns given result
*/
static final class RunnableAdapter<T> implements Callable<T> {
final Runnable task;
final T result;
RunnableAdapter(Runnable task, T result) {
this.task = task;
this.result = result;
}
public T call() {
task.run();
return result;
}
}
简单不失优雅!这就是,大巧不工!
注意:FutureTask 使用的同步锁直接在当前对象上上锁,从而实现线程间通信唤醒。其执行顺序是先改变 state 状态值,再依次唤醒。但是锁一定是这样的,不可能先唤醒再改状态。这里存在一定间隙,即执行线程还未完全返回,获取线程就已经拿到结果了,这是允许的。即没被唤醒不一定执行未完成,被唤醒后一定执行完成了。
但是有一个点我们可以看到,那就是 result 的获取,其实就是传入什么值,就返回值。而如果想在想要改变其结果,唯一的办法是使 result 变量 对 Runnable.run() 可见,从而在 run() 方法中改变其值。这就看你怎么用了!
FutureTask是怎样获取到异步执行结果的?的更多相关文章
- 获取node异步执行结果的方式
拿数据库操作举例: var connection = mysql.createConnection(); connection.query(sql,function(err,rows){xxx} ); ...
- C#异步执行带有返回值和参数的方法,且获取返回值
很多时候需要用到这些小知识点,做做笔记一起成长 下面是需要异步执行的方法 //获取所有的邮件 private List<EmailModel> GetEmailOnlyCount(POP3 ...
- 异步执行任务SimpleAsyncTaskExecutor详解
SimpleAsyncTaskExecutor 异步执行用户任务的SimpleAsyncTaskExecutor.每次执行客户提交给它的任务时,它会启动新的线程,并允许开发者控制并发线程的上限(con ...
- Python开发程序:RPC异步执行命令(RabbitMQ双向通信)
RPC异步执行命令 需求: 利用RibbitMQ进行数据交互 可以对多台服务器进行操作 执行命令后不等待命令的执行结果,而是直接让输入下一条命令,结果出来后自动打印 实现异步操作 不懂rpc的请移步h ...
- Saltstack异步执行命令(十三)
Saltstack异步执行命令 salt执行命令有时候会有超时的问题,就是命令下发下去了,部分主机没有返回信息,这时候就很难判断命令或任务是否执行成功.因此,salt提供异步执行的功能,发出命令后立即 ...
- Jquery ajax 绑定multiselect多选下拉选项,同时异步执行返回值
Jquery ajax 绑定multiselect多选下拉选项,同时异步执行获取返回值 function load(mslt_employees,belongto,mark) {//传入$(#ID) ...
- Go同步和异步执行多个任务封装
同步执行类RunnerAsync 支持返回超时检测,系统中断检测 错误常量定义 //超时错误 var ErrTimeout = errors.New("received timeout&qu ...
- 使用Task异步执行方法_多线程_应用程序池
偶然遇到在执行登录的方法需要发送消息队列导致登录时间过长的问题,从网上查了一些方法,先将一个简单的异步处理程序的小例子展示出来,供大家参考: 备注:该方法是从应用程序程序所在的线程池中获取线程,第一次 ...
- PHP 命令行模式实战之cli+mysql 模拟队列批量发送邮件(在Linux环境下PHP 异步执行脚本发送事件通知消息实际案例)
源码地址:https://github.com/Tinywan/PHP_Experience 测试环境配置: 环境:Windows 7系统 .PHP7.0.Apache服务器 PHP框架:ThinkP ...
随机推荐
- codeforces 572 C. Lengthening Sticks(数学)
题目链接:http://codeforces.com/contest/572/problem/C 题意:给出a,b,c,l要求a+x,b+y,c+z构成三角形,x+y+z<=l,成立的x,y,z ...
- 【Redis】安装、开启以及关闭
一.Linux环境的操作 1.1 下载安装 1.2 启动 1.3 连接Redis客户端 1.4 关闭 二.Windows和Mac下的操作 2.1 下载安装 2.2 启动 2.3 连接客户端 2.4 关 ...
- 【Spring】编程式事务和声明式事务
一.概述 二.准备工作 1. 创建表 2. 创建项目并引入Maven依赖 3. 编写实体类 4. 编写Dao层 5. 业务层 6. XML中的配置 7. 测试 三.编程式事务 1. 在业务层代码上使用 ...
- c语言实现双色球和大乐透
头文件: #include<stdio.h> #include <stdlib.h> #include<string.h> #include <time.h& ...
- 每天学会一点点(map常量)
map常用的声明方式(使用静态代码块): public final static Map map = new HashMap(); static { map.put("key1", ...
- 64位linux编译32位程序
昨天接到的任务,编译64位和32位两个版本的.so动态库给其他部门,我的ubuntu虚拟机是64位的,编译32位时遇到了问题: /usr/bin/ld: cannot find -lstdc++ 最后 ...
- a149: 乘乘樂
题目: 你拿到一个整数,却忍不住想把每个位数都乘在一起.例如看到356就会想要知道3 * 5 * 6的值为何.快写个程序帮帮为了乘数字而快发疯的自己吧! 思路:把这个数每一位%10,并且再将它每次/1 ...
- response向客户端写入数据
1.写入文字: protected void doGet(HttpServletRequest request, HttpServletResponse response) throws Servle ...
- Linux 笔记 - 第十九章 配置 Squid 正向代理和反向代理服务
一.简介 Squid 是一个高性能的代理缓存服务器,对应中文的乌贼,鱿鱼的意思.Squid 支持 FTP,gopher 和 HTTP 协议.和一般的代理缓存软件不同,Squid 用一个单独的,非模块化 ...
- jmeter linux压测报错:Error in NonGUIDriver java.lang.IllegalArgumentException: Problem loading XML from:'/home/server/ptest/disk_out.jmx'.
1.linux环境jmeter与win环境编写脚本的jmeter版本不一致,版本改为一致 2.脚本中存在中文,去除中文 3.脚本中存在类似于jp@gc - Active Threads Over Ti ...