文章目录
补.常用多线程并发获取返回结果方法汇总
描述 | Future | FutureTask | CompletionService | CompletableFuture |
---|---|---|---|---|
原理 | Future接口 | 接口RunnableFuture的唯一实现类,RunnableFuture接口继承自Future+Runnable | 内部通过阻塞队列+FutureTask接口 | JDK8实现了Future, CompletionStage两个接口 |
多任务并发执行 | 支持 | 支持 | 支持 | 支持 |
获取任务结果的顺序 | 按照提交顺序获取结果 | 未知 | 支持任务完成的先后顺序 | 支持任务完成的先后顺序 |
异常捕捉 | 自己捕捉 | 自己捕捉 | 自己捕捉 | 原生API支持,返回每个任务的异常 |
建议 | CPU高速轮询,耗资源,或者阻塞,可以使用,但不推荐 |
功能不对口,并发任务这一块多套一层,不推荐使用 |
推荐使用 ,没有JDK8CompletableFuture之前最好的方案 |
API极端丰富,配合流式编程,推荐使用 ! |
一.概述
- 无论是的
继承Thread
,还是实现Runnable接口
来创建线程。都有一个问题就是:任务完成后无法获取执行结果。如果需要获结果,就必须通过共享变量
或者使用线程通信
的方式来达到效果,这样使用起来就比较麻烦
。Java 1.5
提供了Callable和Future
,通过它们可以在`任务执行完后得到获取结果。
二.Runnable接口
接口里面只声明了一个run()方法
,由于方法返回值为void类型
,所以在执行完任务之后无法返回结果
。
public interface Runnable {
public abstract void run();
}
三.Callable接口
位于java.util.concurrent
包下,里面只声明了一个call()方法:
public interface Callable<V> {
V call() throws Exception;
}
Callable是一个泛型接口(V代表异步任务的返回结果的类型)
,Callable接口可以看作是Runnable接口
的补充,和Runnable相比,该方法有返回值
并允许抛出异常
。
也就是说Future提供了2种功能:
- 可以返回任务执行完成结果
- 可以抛出异常
五.Future接口
位于java.util.concurrent
包下,是一个泛型接口(V代表获取异步任务的返回结果的类型)
。用于来获取
一个可能还没有完成的异步任务的结果
,结合Callback接口
可以在任务执行成功或失败后获取call()方法
的返回结果
public interface Future<V> {
//用来取消任务,如果取消任务成功,则返回true,如果取消任务失败,则返回false
//参数mayInterruptIfRunning表示: 是否允许取消正在执行却没有执行完毕的任务
/*
如果设置true,则表示可以取消正在执行过程中的任务。
1. 如果任务已经完成,则无论mayInterruptIfRunning为true还是false,此方法肯定返回false,
2. 如果任务正在执行,若mayInterruptIfRunning设置为true,则返回true, 若mayInterruptIfRunning设置为false,则返回false
3. 如果任务还没有执行,则无论mayInterruptIfRunning为true还是false,肯定返回true。
*/
boolean cancel(boolean mayInterruptIfRunning);
//表示任务是否被取消成功,如果在任务正常完成前被取消成功,则返回 true。
boolean isCancelled();
//用于表示任务是否完成,完成返回true
boolean isDone();
//用来获取执行结果,这个方法会产生阻塞,会一直等到任务执行完毕才返回,可能会抛出异常。
V get() throws InterruptedException, ExecutionException;
//在指定的时间内等待task完成,并获取执行结果。可能抛出异常(含TimeoutException)
V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
}
也就是说Future提供了三种功能:
- 判断任务是否完成
- 能够中断任务
- 能够获取任务执行结果
使用Callable+Future并发执行任务并获取结果:
- 使用线程池以
submit()方式
提交Callable接口
任务,返回Future接口
,添加进list
,最后遍历FutureList
且内部使用while轮询
,并发获取结果
public class CallableFutureDemo {
public static void main(String[] args) {
Long start = System.currentTimeMillis();
//开启多线程
ExecutorService exs = Executors.newFixedThreadPool(10);
try {
//结果集
List<Integer> list = new ArrayList<Integer>();
//任务集
List<Future<Integer>> futureList = new ArrayList<Future<Integer>>();
//1.高速提交10个任务,每个任务返回一个Future入list
for (int i = 0; i < 10; i++) {
futureList.add(exs.submit(new CallableTask(i + 1)));
}
Long getResultStart = System.currentTimeMillis();
System.out.println("结果归集开始时间=" + new Date());
//2.结果归集,用迭代器遍历futureList,高速轮询(模拟实现了并发),任务完成就移除
while (futureList.size() > 0) {
Iterator<Future<Integer>> iterable = futureList.iterator();
//遍历一遍
while (iterable.hasNext()) {
Future<Integer> future = iterable.next();
//如果任务完成取结果,否则判断下一个任务是否完成
if (future.isDone() && !future.isCancelled()) {
//获取结果
Integer i = future.get();
System.out.println("任务i=" + i + "获取完成,移出任务队列!" + new Date());
list.add(i);
//任务完成移除任务
iterable.remove();
} else {
Thread.sleep(1);//避免CPU高速运转,这里休息1毫秒,CPU纳秒级别
}
}
}
System.out.println("list=" + list);
System.out.println("总耗时=" + (System.currentTimeMillis() - start) + ",取结果归集耗时=" + (System.currentTimeMillis() - getResultStart));
} catch (Exception e) {
e.printStackTrace();
} finally {
exs.shutdown();
}
}
static class CallableTask implements Callable<Integer> {
Integer i;
public CallableTask(Integer i) {
super();
this.i = i;
}
@Override
public Integer call() throws Exception {
if (i == 1) {
Thread.sleep(3000);//任务1耗时3秒
} else if (i == 5) {
Thread.sleep(5000);//任务5耗时5秒
} else {
Thread.sleep(1000);//其它任务耗时1秒
}
System.out.println("task线程:" + Thread.currentThread().getName() + "任务i=" + i + ",完成!" + new Date());
return i;
}
}
}
结论:
- 当我们使用
线程池
提交一个Callable任务
后,会返回一个Future对象
,然后在主线程某个时刻调用Future的get()方法,就可以获得异步执行的结果。 - 在调用get()时,如果异步任务已经完成,我们就直接获得结果。如果异步任务还没有完成,那么get()
会阻塞
,直到任务完成后才返回结果。
开启定长为10的线程池,任务1耗时3秒,任务5耗时5秒,其他1秒。控制台打印如下:
结果归集开始时间=Wed Mar 03 18:52:19 CST 2021 //起始52秒
task线程:pool-1-thread-3任务i=3,完成!Wed Mar 03 18:52:20 CST 2021
task线程:pool-1-thread-9任务i=9,完成!Wed Mar 03 18:52:20 CST 2021
task线程:pool-1-thread-7任务i=7,完成!Wed Mar 03 18:52:20 CST 2021
task线程:pool-1-thread-6任务i=6,完成!Wed Mar 03 18:52:20 CST 2021
task线程:pool-1-thread-8任务i=8,完成!Wed Mar 03 18:52:20 CST 2021
task线程:pool-1-thread-10任务i=10,完成!Wed Mar 03 18:52:20 CST 2021
task线程:pool-1-thread-2任务i=2,完成!Wed Mar 03 18:52:20 CST 2021
task线程:pool-1-thread-4任务i=4,完成!Wed Mar 03 18:52:20 CST 2021
任务i=6获取完成,移出任务队列!Wed Mar 03 18:52:20 CST 2021 // 一般任务耗时1秒,19+1=20,验证通过!
任务i=7获取完成,移出任务队列!Wed Mar 03 18:52:20 CST 2021
任务i=8获取完成,移出任务队列!Wed Mar 03 18:52:20 CST 2021
任务i=9获取完成,移出任务队列!Wed Mar 03 18:52:20 CST 2021
任务i=10获取完成,移出任务队列!Wed Mar 03 18:52:20 CST 2021
任务i=2获取完成,移出任务队列!Wed Mar 03 18:52:20 CST 2021
任务i=3获取完成,移出任务队列!Wed Mar 03 18:52:20 CST 2021
任务i=4获取完成,移出任务队列!Wed Mar 03 18:52:20 CST 2021
task线程:pool-1-thread-1任务i=1,完成!Wed Mar 03 18:52:22 CST 2021
任务i=1获取完成,移出任务队列!Wed Mar 03 18:52:22 CST 2021 //任务1 耗时3秒 19+3=22,验证通过!
task线程:pool-1-thread-5任务i=5,完成!Wed Mar 03 18:52:24 CST 2021
任务i=5获取完成,移出任务队列!Wed Mar 03 18:52:24 CST 2021 //任务5 耗时5秒 19+5=24,验证通过!
list=[6, 7, 8, 9, 10, 2, 3, 4, 1, 5] //多执行几遍,最后2个总是1,5最后加进去的,可实现按照任务完成先后顺序获取结果!
总耗时=5014,取结果归集耗时=5003 //符合逻辑,10个任务,定长10线程池,其中一个任务耗时3秒,一个任务耗时5秒,由于并发高速轮询,耗时取最长5秒
建议: 任务并行
且按照完成顺序
获取结果。使用很普遍,老少皆宜,就是CPU有消耗,可以使用!
六.FutureTask类
1.概述
因为Future只是一个接口,所以是无法直接用来创建对象使用的,因此就有了下面的FutureTask
FutureTask<T>
(T代表异步任务的返回结果类型
)类实现了RunnableFuture接口(唯一实现)
,该接口继承自Runnable和Future接口
,弥补了`Future必须用线程池提交返回Future的缺陷,实现功能如下:
- 实现了
Runnable接口
,可开启单个线程执行。 - 实现了
Future<v>接口
,可接受Callable接口的返回值,futureTask.get()阻塞获取结果。
因此可以提交给
Executor线程池
执行,也可以通过Thread包装来直接执行
2.怎么使用
2.1. Thread方式
public static void main(String[] args) throws Exception {
//直接通过匿名类方式创建带返回值的任务
Callable<Integer> call = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println("子线程在进行计算");
int sum = 0;
for (int i = 0; i < 100; i++) {
//每次累加休眠10毫秒 10 * 100 = 1000ms
Thread.sleep(10);
sum += i;
}
return sum;
}
};
//创建获取返回值的任务
FutureTask<Integer> futureTask = new FutureTask<>(call);
//通过Thread启动任务
Thread thread = new Thread(futureTask);
//启动任务
thread.start();
//轮询判断任务是否完成,未完成则休眠没一次休眠500ms
while (!futureTask.isDone()) {
System.out.println("当前任务未完成,休眠500ms");
Thread.sleep(500);
}
System.out.println("最终FutureTask返回结果:" + futureTask.get());
}
2.2. 线程池方式
public static void main(String[] args) {
//直接通过匿名类方式创建带返回值的任务
Callable<String> callable1 = new Callable<String>() {
@Override
public String call() throws Exception {
Thread.sleep(2000);
return Thread.currentThread().getName();
}
};
//直接通过匿名类方式创建带返回值的任务
Callable<String> callable2 = new Callable<String>() {
@Override
public String call() throws Exception {
Thread.sleep(3000);
return Thread.currentThread().getName();
}
};
// 将Callable写的任务封装到一个由执行者调度的FutureTask对象
FutureTask<String> futureTask1 = new FutureTask<>(callable1);
FutureTask<String> futureTask2 = new FutureTask<>(callable2);
// 创建线程池并返回ExecutorService实例
ExecutorService executor = Executors.newFixedThreadPool(2);
executor.execute(futureTask1); //执行任务1
executor.execute(futureTask2);//执行任务2
//同时开启了两个任务
long startTime = System.currentTimeMillis();
while (true) {
try {
// 两个任务都完成,关闭线程池和服务
if (futureTask1.isDone() && futureTask2.isDone()) {
System.out.println("FutureTask1 and FutureTask1 执行完成");
executor.shutdown();
return;
}
// 任务1完成则打印结果
if (!futureTask1.isDone()) {
System.out.println("FutureTask1 执行完成=" + futureTask1.get());
}
System.out.println("等待 FutureTask2 执行完成");
//200毫秒获取不到FutureTask2结果,则抛出异常
String s = futureTask2.get(200L, TimeUnit.MILLISECONDS);
if (s != null) {
System.out.println("FutureTask2 执行完成=" + s);
}
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
System.out.println("耗时:"+(System.currentTimeMillis() - startTime)+"ms");
}
}
2.3. 使用Callable+FutureTask并发执行任务并获取结果
和上面的 使用Callable+FutureTask并发执行任务并获取结果差不多,就是将Future替换成了FutureTask,同时每次替换的时候保存当前提交的 FutureTask
public class FutureTaskDemo {
public static void main(String[] args) {
Long start = System.currentTimeMillis();
//开启多线程
ExecutorService executor = Executors.newFixedThreadPool(10);
try {
//结果集
List<Integer> list = new ArrayList<Integer>();
//任务集
List<FutureTask<Integer>> futureList = new ArrayList<FutureTask<Integer>>();
//启动线程池,10个任务固定线程数为5
for (int i = 0; i < 10; i++) {
FutureTask<Integer> futureTask = new FutureTask<Integer>(new CallableTask(i + 1));
//提交任务,添加返回,Runnable特性
executor.submit(futureTask);
//Future特性
futureList.add(futureTask);
}
Long getResultStart = System.currentTimeMillis();
System.out.println("结果归集开始时间=" + new Date());
//2.结果归集,用迭代器遍历futureList,高速轮询(模拟实现了并发),任务完成就移除
while (futureList.size() > 0) {
Iterator<FutureTask<Integer>> iterable = futureList.iterator();
//遍历一遍
while (iterable.hasNext()) {
FutureTask<Integer> future = iterable.next();
//如果任务完成取结果,否则判断下一个任务是否完成
if (future.isDone() && !future.isCancelled()) {
//获取结果
Integer i = future.get();
System.out.println("任务i=" + i + "获取完成,移出任务队列!" + new Date());
list.add(i);
//任务完成移除任务
iterable.remove();
} else {
Thread.sleep(1);//避免CPU高速运转,这里休息1毫秒,CPU纳秒级别
}
}
}
System.out.println("list=" + list);
System.out.println("总耗时=" + (System.currentTimeMillis() - start) + ",取结果归集耗时=" + (System.currentTimeMillis() - getResultStart));
} catch (Exception e) {
e.printStackTrace();
} finally {
executor.shutdown();
}
}
static class CallableTask implements Callable<Integer> {
Integer i;
public CallableTask(Integer i) {
super();
this.i = i;
}
@Override
public Integer call() throws Exception {
if (i == 1) {
Thread.sleep(3000);//任务1耗时3秒
} else if (i == 5) {
Thread.sleep(5000);//任务5耗时5秒
} else {
Thread.sleep(1000);//其它任务耗时1秒
}
System.out.println("task线程:" + Thread.currentThread().getName() + "任务i=" + i + ",完成!" + new Date());
return i;
}
}
}
开启定长为10的线程池,任务1耗时3秒,任务5耗时5秒,其他1秒。控制台打印如下:
结果归集开始时间=Wed Mar 03 18:52:19 CST 2021 //起始52秒
task线程:pool-1-thread-3任务i=3,完成!Wed Mar 03 18:52:20 CST 2021
task线程:pool-1-thread-9任务i=9,完成!Wed Mar 03 18:52:20 CST 2021
task线程:pool-1-thread-7任务i=7,完成!Wed Mar 03 18:52:20 CST 2021
task线程:pool-1-thread-6任务i=6,完成!Wed Mar 03 18:52:20 CST 2021
task线程:pool-1-thread-8任务i=8,完成!Wed Mar 03 18:52:20 CST 2021
task线程:pool-1-thread-10任务i=10,完成!Wed Mar 03 18:52:20 CST 2021
task线程:pool-1-thread-2任务i=2,完成!Wed Mar 03 18:52:20 CST 2021
task线程:pool-1-thread-4任务i=4,完成!Wed Mar 03 18:52:20 CST 2021
任务i=6获取完成,移出任务队列!Wed Mar 03 18:52:20 CST 2021 // 一般任务耗时1秒,19+1=20,验证通过!
任务i=7获取完成,移出任务队列!Wed Mar 03 18:52:20 CST 2021
任务i=8获取完成,移出任务队列!Wed Mar 03 18:52:20 CST 2021
任务i=9获取完成,移出任务队列!Wed Mar 03 18:52:20 CST 2021
任务i=10获取完成,移出任务队列!Wed Mar 03 18:52:20 CST 2021
任务i=2获取完成,移出任务队列!Wed Mar 03 18:52:20 CST 2021
任务i=3获取完成,移出任务队列!Wed Mar 03 18:52:20 CST 2021
任务i=4获取完成,移出任务队列!Wed Mar 03 18:52:20 CST 2021
task线程:pool-1-thread-1任务i=1,完成!Wed Mar 03 18:52:22 CST 2021
任务i=1获取完成,移出任务队列!Wed Mar 03 18:52:22 CST 2021 //任务1 耗时3秒 19+3=22,验证通过!
task线程:pool-1-thread-5任务i=5,完成!Wed Mar 03 18:52:24 CST 2021
任务i=5获取完成,移出任务队列!Wed Mar 03 18:52:24 CST 2021 //任务5 耗时5秒 19+5=24,验证通过!
list=[6, 7, 8, 9, 10, 2, 3, 4, 1, 5] //多执行几遍,最后2个总是1,5最后加进去的,可实现按照任务完成先后顺序获取结果!
总耗时=5014,取结果归集耗时=5003 //符合逻辑,10个任务,定长10线程池,其中一个任务耗时3秒,一个任务耗时5秒,由于并发高速轮询,耗时取最长5秒
建议:Callbale+FutureTask
多线程并发执行并结果归集,这里多套一层FutureTask比较鸡肋
(还是直接返回Future简单明了)不建议使用
。
3.FutureTask类的实现
3.1.属性和构造方法
/*
* 可能的状态变更:
* NEW -> COMPLETING -> NORMAL
* NEW -> COMPLETING -> EXCEPTIONAL
* NEW -> CANCELLED
* NEW -> INTERRUPTING -> INTERRUPTED
*/
//就是当前task的状态
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; //已中断
//底层的Callable对象,它的call方法执行后会产生执行结果,执行完成后该属性会置为null*/
private Callable<V> callable;
//存储get()方法返回的执行结果或是抛出的异常
private Object outcome;
// 执行该Callable任务的线程
private volatile Thread runner;
//CAS进行更新的waiting threads,WaitNode节点形成了一个链表,表示正在等待获取执行结果的线程
private volatile WaitNode waiters;
//等待节点:存储等待执行获取任务执行结果的线程们
static final class WaitNode {
//表示等待结果的线程
volatile Thread thread;
//表示下一个等待节点(线程)
//表示等待任务执行结果的等待栈(数据结构是单向链表)
// WaitNode是一个简单的静态内部类,一个成员变量thread表示等待结果的线程,另一个成员变量next表示下一个等待节点(线程)。
volatile WaitNode next;
WaitNode() {
thread = Thread.currentThread(); }
}
// 可直接操作内存的类。详情百度
private static final sun.misc.Unsafe UNSAFE;
//UNSAFE使用的,state成员变量在FutureTask类中的偏移量
private static final long stateOffset;
//UNSAFE使用的,runner成员变量在FutureTask类中的偏移量
private static final long runnerOffset;
//UNSAFE使用的,waiters成员变量在FutureTask类中的偏移量
private static final long waitersOffset;
static {
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class<?> k = FutureTask.class;
stateOffset = UNSAFE.objectFieldOffset
(k.getDeclaredField("state"));
runnerOffset = UNSAFE.objectFieldOffset
(k.getDeclaredField("runner"));
waitersOffset = UNSAFE.objectFieldOffset
(k.getDeclaredField("waiters"));
} catch (Exception e) {
throw new Error(e);
}
}
//构造函数,给定一个Callable任务
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // 初始状态
}
//创建一个执行Runnable的Future,返回结果在参数中的result里,如果不需要结果,传入null就行了
public FutureTask(Runnable runnable, V result) {
//将Runnable和result包装为Callable对象
this.callable = Executors.callable(runnable, result);
this.state = NEW; // 初始状态
}
FutureTask的构造方法会初始化callable和state
,它有2个构造方法, 分别接受Callable和Runnable类型的待执行任务
。但对于Runnable类型参数,它会调用Executors.callable
将Runnable转换为Callable类型实例,以便于统一处理。
- Executors.callable方法也很简单,它就返回了一个
Callable的实现类RunnableAdapter类型的对象
。
public static <T> Callable<T> callable(Runnable task, T result) {
if (task == null)
throw new NullPointerException();
return new RunnableAdapter<T>(task, 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;
}
}
3.2.运行状态
如上面的源码所示,FutureTask使用一个volatile int state
的变量作为同步状态
state所有可能的取值有7个
,分别如下:
- NEW = 0;
初始状态
,FutureTask刚被创建,正在执行中都是该状态。 - COMPLETING = 1;
中间状态
,表示执行完成正在对结果进行赋值
,或正在处理异常
- NORMAL = 2;
终止状态
,表示执行完成
,结果已经被赋值
。 - EXCEPTIONAL = 3;
异常终止状态
,表示执行过程已经被异常打断
。 - CANCELLED = 4;
取消终止状态
,表示执行过程已经被cancel操作终止
。 - INTERRUPTING = 5;
中间状态
,表示执行过程已开始并且被中断
,正在修改状态
。 - INTERRUPTED = 6;
终止状态
,表示执行过程已开始并且被中断
,目前已完全停止
。
初始状态是New
状态,之后运行状态的变化只会发生在set()、setException()、cancel()方法
时,可能出现的状态转换有如下情形:
- NEW --> COMPLETING --> NORMAL :表示任务正常执行完成
- NEW --> COMPLETING --> EXCEPTIONAL:表示任务执行过程种出现异常
- NEW --> CANCELLED :表示任务被取消执行
- NEW --> INTERRUPTING --> INTERRUPTED :表示任务的执行过程被中断
3.3.关键源码
3.3.1.两个状态检查方法
该类的两个状态检查方法如下:
// 检查是否已经取消,当状态处于Cancelled、Interrupting、Interrupted时表示已经取消
public boolean isCancelled() {
return state >= CANCELLED;
}
// 检查任务是否执行完成,只要不是New状态就代表已经执行了
public boolean isDone() {
return state != NEW;
}
3.3.2.run()方法的实现
run()方法的实现如下:
- run()方法用于
开始执行FututeTask
,执行线程为当前线程
,该方法会将任务从New状态转换为其他状态
。- run方法中可以看出,
只要不是New状态就代表已经执行完了
,因为状态设置
是在call()方法执行完后
进行的。
- run方法中可以看出,
public void run() {
//如果不是处于New状态或者设置执行线程为当前线程失败,就直接返回
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 {
//1.调用callable.call(),执行task,获取返回值
result = c.call();
ran = true;
} catch (Throwable ex) {
//出现异常就将结果置为空
result = null;
ran = false;
//2.FutureTask的异常处理关键: 设置出现的异常
setException(ex);
}
if (ran)
//3.成功执行后设置执行结果
set(result);
}
} finally {
// 当前封装的线程设为null,
runner = null;
// state must be re-read after nulling runner to prevent leaked interrupts
int s = state;
//任务处于中断中的状态,则进行中断操作
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
//异常设置
protected void setException(Throwable t) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
//将状态位设置成中间状态COMPLETING
//将执行结果设置为异常,完成任务,设置状态为Exceptional
outcome = t;//设置输出为正常返回结果
UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // 将状态更为最终状态NORMAL
finishCompletion();//同下面讲解
}
}
//正常返回值设置
protected void set(V v) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
//将状态位设置成中间状态COMPLETING
//设置执行结果,将状态设为成功执行的Normal
outcome = v;//设置输出为正常返回结果
UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // 将状态更为最终状态NORMAL
finishCompletion();//同下面讲解
}
}
//由中间状态到最终状态变更时,都需进行的操作,会在set()、setException()、cancel()被调用
//在任务被取消、正常完成或执行异常时会调用finishCompletion()方法,从而唤醒等待队列中的线程。
private void finishCompletion() {
// assert state > COMPLETING;
for (WaitNode q; (q = waiters) != null;) {
if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
//尝试将waiters全部置为null
for (;;) {
//将waiters下对应的链式thread挨个唤醒
Thread t = q.thread;
if (t != null) {
q.thread = null;
LockSupport.unpark(t);//唤醒操作,LockSupport.park()阻塞
}
WaitNode next = q.next;
if (next == null)
break;
q.next = null; // unlink to help gc
q = next;
}
break;
}
}
done();
callable = null; // 减少内存占用
}
//子类可重写该方法,实现回调
protected void done() {
}
// 确保cancel(true)产生的中断发生在run或runAndReset方法过程中。
private void handlePossibleCancellationInterrupt(int s) {
if (s == INTERRUPTING)
while (state == INTERRUPTING)
//将当前线程从执行状态退出,等待中断
Thread.yield();
}
3.3.3.cancel()方法的实现
cancel()方法的实现:
- 取消task,根据boolean值来决定是否可进行中断操作
//cancel用于取消任务
public boolean cancel(boolean mayInterruptIfRunning) {
//如果状态不为NEW,且无法将状态更新为INTERRUPTING或CANCELLED,则直接返回取消失败
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 {
// final state
//设置最终状态为Interrupted
UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
}
}
} finally {
//执行清理工作,包括移除正在等待的线程,激活done()方法,将callable任务置为null
finishCompletion(); //同面讲解
}
return true;
}
3.3.4.get()和get(long timeout, TimeUnit unit)方法
get()和get(long timeout, TimeUnit unit)方法实现:
- 注意调用get方法的线程是另外一个线程,可能会有好多其他线程在
等待FutureTask的执行结果
,那么就把他们封装成一个个WaitNode(结构上面有讲解)去等待返回结果
。
public V get() throws InterruptedException, ExecutionException {
int s = state;
//处于New或Completing状态
if (s <= COMPLETING)
//阻塞等待执行完成
s = awaitDone(false, 0L);
//根据执行状态返回执行结果
return report(s);
}
public V get(long timeout, TimeUnit unit)throws InterruptedException, ExecutionException, TimeoutException {
if (unit == null)
throw new NullPointerException();
int s = state;
//如果是中间状态,就等待task完成,如果超过了指定时间,task仍未完成,则抛出超时异常
if (s <= COMPLETING &&(s = awaitDone(true, unit.toNanos(timeout))) <= COMPLETING)
throw new TimeoutException();
//没超时就报告结果
return report(s);
}
//报告执行结果,返回的可以是异常,或是正常结果
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);
}
//线程status为NEW和COMPLETING的时候,会进入awaitDone方法,表示要等待完成。
//等待完成,可能是是中断、异常、正常完成,timed:true,考虑等待时长,false:不考虑等待时长
private int awaitDone(boolean timed, long nanos)
throws InterruptedException {
final long deadline = timed ? System.nanoTime() + nanos : 0L; //如果设置了超时时间
WaitNode q = null;
boolean queued = false;
//死循环不断尝试获取结果
for (;;) {
/**
* 有优先级顺序
* 1、如果线程已中断,则直接将当前节点q从waiters中移出
* 2、如果state已经是最终状态了,则直接返回state
* 3、如果state是中间状态(COMPLETING),意味很快将变更过成最终状态,让出cpu时间片即可
* 4、如果发现尚未有节点,则创建节点
* 5、如果当前节点尚未入队,则将当前节点放到waiters中的首节点,并替换旧的waiters
* 6、线程被阻塞指定时间后再唤醒
* 7、线程一直被阻塞直到被其他线程唤醒
*
*/
//1.如果当前get()方法的线程被中断了,那么就从等待队列中移出,抛出异常
if (Thread.interrupted()) {
removeWaiter(q);
throw new InterruptedException();
}
int s = state;
//2.如果处于已完成态,直接返回完成状态就好了
if (s > COMPLETING) {
if (q != null)
q.thread = null;
return s;
}
else if (s == COMPLETING) // 3.处于未完成态,就让出当前线程,重新竞争CPU
Thread.yield();
else if (q == null) // 4.如果当前线程还没有加入到等待队列,就新建一个WaitNode
q = new WaitNode();
else if (!queued) // 5.添加到等待队列的头部
queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
q.next = waiters, q);
else if (timed) {
// 6.超时等待
nanos = deadline - System.nanoTime();
if (nanos <= 0L) {
//如果超出了等待时间,就移出队列
removeWaiter(q);
return state;
}
//阻塞当前线程一段时间
LockSupport.parkNanos(this, nanos);
}
else
//7.阻塞当前线程,等待被唤醒
LockSupport.park(this);
}
}
//移除等待执行的节点
private void removeWaiter(WaitNode node) {
if (node != null) {
node.thread = null; //将需要去除的节点,thread赋值为null
retry:
for (;;) {
for (WaitNode pred = null, q = waiters, s; q != null; q = s) {
s = q.next;
if (q.thread != null)
pred = q;
else if (pred != null) {
//q.thread==null,表示该节点是需要在队列中去除的节点,故直接将pred.next=s,重组队列
pred.next = s;
if (pred.thread == null) //如果这个pred节点恰好是需要去除的节点,则进行循环,重组队列
continue retry;
}
else if (!UNSAFE.compareAndSwapObject(this, waitersOffset, q, s))
continue retry;
}
break;
}
}
上面方法中awaitDone
是get()方法的核心
,每一个等待获取结果的线程
最终都会在该方法上阻塞
。它不断循环执行核心方法,执行流程是:
- 先检查调用
get()
方法的那个线程
是否已经中断
,如果中断了,那就从等待队列
中删除该节点
,抛出异常
。 - 如果处于
已完成状态
,也就是Normal、Exceptional、Cancelled、Interrupting、Interrupted中的某一种,那么就直接返回执行状态
就好了 - 如果处于
Completing正在执行
中,就让出当前线程占用的CPU时间
,防止资源量浪费,等待任务执行完成
。 - 如果还
未加入等待队列
,就将自己加入到等待队列头部
- 如果
有超时时间
,就阻塞
一段时间,等待任务执行完
后唤醒该操作,重新回到循环头部
- 如果
没有超时时间,
就直接阻塞
,等待被唤醒.
相关文章