Future
简单来说就是利用线程达到异步的效果,同时还可以获取子线程的返回值。
比如当做一定运算的时候,运算过程可能比较耗时,有时会去查数据库,或是繁重的计算,比如压缩、加密等,在这种情况下,如果我们一直在原地等待方法返回,显然是不明智的,整体程序的运行效率会大大降低。
我们可以把运算的过程放到子线程去执行,再通过 Future 去控制子线程执行的计算过程,最后获取到计算结果。这样一来就可以把整个程序的运行效率提高,是一种异步的思想。
Demo
下面举一个利用Futrue的例子:
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask future = new FutureTask<String>(new Callable<String>() {
public String call() throws Exception {
//模拟需要计算或其他耗时的操作
TimeUnit.SECONDS.sleep(3);
return "我是返回的结果...";
}
});
//使用线程池
ExecutorService executorService = Executors.newFixedThreadPool(3);
executorService.submit(future);
//##########其他不需要返回结果的业务逻辑###############
//获取执行的结果
String ret = (String) future.get();
//##########其他需要返回结果的业务逻辑###############
System.out.println(ret);
}
上面的例子很简单就是利用Callable定义了一个线程,去执行一个有耗时的任务,然后提交到线程池中执行,最后使用Future.get()方法获取执行的结果。下面就开始进行源码分析:
在分析之前需要先了解下FutureTask中定义的执行进度标志state的值
//执行进度标志量
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;
执行任务分析
上面的例子中使用的submit(Runnable task)提交的线程任务,那就从它开始:
AbstractExecutorService
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
//new了一个FutureTask对象
RunnableFuture<Void> ftask = newTaskFor(task, null);
//交由execute方法执行
execute(ftask);
return ftask;
}
注意:我们在使用submit()提交任务的时候其实已经是FutureTask对象了,至于在submit中又new了一个FutureTask是因为还有另一种提交任务的方式,如下:
Future<Object> future = executorService.submit(new Callable<Object>() {
public Object call() throws Exception {
//模拟需要计算或其他耗时的操作
TimeUnit.SECONDS.sleep(3);
return "我是返回的结果...";
}
});
继续跟踪execute(Runnable var1)可以找到ServerImpl.DefaultExecutor类中此方法
public void execute(Runnable var1) {
var1.run();
}
从这个方法中可以看到,是调用的线程的run()方法,执行任务
继续跟踪就会找到实际执行的地方:
FutureTask
public void run() {
//省略无关代码。。。。。。
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
//1. 真正执行我们定义的call()中的代码
result = c.call();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
if (ran)
//2. 设置返回结果
set(result);
}
} finally {
//省略。。。。
}
}
上面代码中我们只需要知道两步,
- 就是执行线程的call()获取返回值;
- 执行set()把返回值记录下来,并修改state的状态;
看一下set()方法:
protected void set(V v) {
//1. 设置state的值为complete,证明任务执行完成,但是还没准备好返回值
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
//2. 记录返回值
outcome = v;
//3. 设置state的值为normal,证明任务执行完成,也可以获取返回值了
UNSAFE.putOrderedInt(this, stateOffset, NORMAL);
// 4. 把获取结果 挂起的线程 返回
finishCompletion();
}
}
set()方法中执行了四步,1和3是使用UnSafe的cas修改state的值,第2步就是赋值没什么说的,下面来看第4步finishCompletion()
private void finishCompletion() {
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;
q = next;
}
break;
}
}
done();
callable = null; // to reduce footprint
}
这个方法其实就是遍历WaitNode链表(WaitNode 一个单链表),然后通过LockSupport.unpark(thread)方法,返回 在因结果还未返回 而获取结果 所挂起的线程。(不理解可以继续看,下面获取结果时会有解释)
任务的执行到这里就结束了,下面来看看获取结果的源码:
获取结果分析
从上面的小Demo中知道,获取线程的执行结果是使用的Future的get()方法,下面进入这个方法瞧瞧:
public V get() throws InterruptedException, ExecutionException {
int s = state;
// 1. 如果结果还没返回,就挂起线程
if (s <= COMPLETING)
s = awaitDone(false, 0L);
// 2. 结果返回 ,就获取结果并返回
return report(s);
}
获取结果只要两步:
- 如果结果还没返回,就使用awaitDone()挂起线程
- 结果返回 ,就使用report()获取结果并返回
首先看awaitDone()方法:
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;
//4. 任务执行完成,返回state的值,结束自旋
if (s > COMPLETING) {
if (q != null)
q.thread = null;
return s;
}
else if (s == COMPLETING) // cannot time out yet
Thread.yield();
// 1. 创建一个WaitNode节点
else if (q == null)
q = new WaitNode();
//2. 把新创建的节点加入到链表的表末尾
else if (!queued)
queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
q.next = waiters, q);
//3. 挂起当前线程
else if (timed) {
nanos = deadline - System.nanoTime();
if (nanos <= 0L) {
removeWaiter(q);
return state;
}
LockSupport.parkNanos(this, nanos);
}
else
LockSupport.park(this);
}
}
其实每一个调用get()的线程(在任务还没执行结束的时候),都会通过for循环3次来完成下面这三步:
- 创建一个WaitNode节点
- 把新创建的节点加入到链表的表末尾
- 使用LockSupport.park(this)挂起当前线程
当执行完第三步,线程就会被挂起,只有当任务执行结束 调用上面说过的finishComplete()方法,把挂起的线程返回,for循环才会继续执行,也就是会执行第4步。
最后就可以调用report(state)获取返回值了
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);
}
这个方法很简单就不解释了。
以上只是我的理解,如果理解有误,还请指出!
又学习到了一个知识点。。。。。。。。。。