一、Future介绍
java创建线程的方式,一般常用的是Thread,Runnable。如果需要当前处理的任务有返回结果的话,需要使用Calabble。
Calabble运行需要配合Future。
Future是一个接口,一般会使用FutureTask实现类去接收Callable任务的返回结果。
FutureTask存在一些问题,同步非阻塞执行的任务,不会主动通知返回结果是什么。
二、FutureTask使用
Callable是你要执行的任务。
FutureTask是存放任务返回结果的位置。
java
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<Integer> futureTask = new FutureTask<>(()->{
System.out.println("hello world");
Thread.sleep(1000);
return 123+11;
});
Thread t= new Thread(futureTask);
t.start();
System.out.println("main thread 线程启动了t线程处理任务");
Integer i = futureTask.get();
System.out.println("main thread 拿到结果"+i);
}
三、FutureTask源码分析
分析FutureTask,首先需要查看他的核心属性。
java
/*
* NEW -> COMPLETING -> NORMAL 任务正常执行,返回结果正常
* NEW -> COMPLETING -> EXCEPTIONAL 任务正常执行,返回结果异常
* NEW -> CANCELLED 任务直接被取消的流程
* NEW -> INTERRUPTING -> INTERRUPTED 任务被中断
*/
//代表当前任务的状态
private volatile int state;
private static final int NEW = 0;//任务的初始化
private static final int COMPLETING = 1;//Callable的结果正在封装给当前的FutureTask
private static final int NORMAL = 2;//任务正常结束
private static final int EXCEPTIONAL = 3;//执行任务时,发生了异常
private static final int CANCELLED = 4;//任务被取消了
private static final int INTERRUPTING = 5;//线程的中断状态被设置未了true(线程还在运行)
private static final int INTERRUPTED = 6;//线程被中断了。
//当前要执行的任务
private Callable<V> callable;
//存放任务返回结果的属性,也就是futureTask.get需要获取的结果
private Object outcome; // non-volatile, protected by state reads/writes
//执行任务的线程
private volatile Thread runner;
//单向链表,存放通过get方法挂起等待的线程
private volatile WaitNode waiters;
t.start之后,如何执行Callable的call方法,是通过run方法执行的call方法
java
//futureTask重新了run方法,执行流程,最终会执行callable的call方法
public void run() {
//保障任务的状态是new才可以运行
//基于cas的方式,将当前的线程设置为runner
if (state != NEW ||
!RUNNER.compareAndSet(this, null, Thread.currentThread()))
return;
//准备执行任务
try {
//要执行的任务c
Callable<V> c = callable;
//任务不为null且任务的状态还处于new
if (c != null && state == NEW) {
//放返回结果
V result;
//任务执行是否为正常结束
boolean ran;
try {
//运行call方法,拿到返回结果封装到result中
result = c.call();
//正常返回,ran设置为true
ran = true;
} catch (Throwable ex) {
//结果为null
result = null;
//异常返回,ran设置为false
ran = false;
//设置异常信息
setException(ex);
}
if (ran)
//正常执行结束,设置返回结果
set(result);
}
} finally {
//将执行任务的runner设置为空
runner = null;
//拿到状态
int s = state;
//中断要做一些后续处理
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
//设置返回结果
protected void set(V v) {
//首先要将任务状态从new设置为completing
if (STATE.compareAndSet(this, NEW, COMPLETING)) {
//正常的返回结果,将返回结果设置给outcome
outcome = v;
//将状态修改为normal,代表正常结束
STATE.setRelease(this, NORMAL); // final state
//后面再说
finishCompletion();
}
}
get方法获取返回结果
java
public V get() throws InterruptedException, ExecutionException {
//拿状态
int s = state;
//满足这个状态代表现在可能还没有返回结果
if (s <= COMPLETING)
//尝试挂起线程,等待拿结果
s = awaitDone(false, 0L);
return report(s);
}
//线程要等待任务执行结果拿返回结果,等待任务执行的状态变为大于COMPLETING状态
private int awaitDone(boolean timed, long nanos)
throws InterruptedException {
//计算deadline,如果是get(),就是0,如果是get(time,unit)那就追加当前系统时间
final long deadline = timed ? System.nanoTime() + nanos : 0L;
//构建waitNode
WaitNode q = null;
boolean queued = false;
//死循环
for (;;) {
// 1. 检查中断:get的线程是否中断
if (Thread.interrupted()) {
//将当前节点从waiter中移除
removeWaiter(q);
//并且抛出中断异常
throw new InterruptedException();
}
// 2. 检查任务状态:拿到现在任务的状态
int s = state;
//判断任务是否已经执行结束了
if (s > COMPLETING) {
//如果设置为waitNode,直接移除waitNode的线程
if (q != null)
q.thread = null;
//返回当前任务的状态
return s;
}
//如果任务的状态处于COMPLETING,线程让渡
else if (s == COMPLETING) {
//COMPLETING持续时间短,只需要做一手线程的让渡即可。
Thread.yield();
}
// 3. 创建等待节点:现在线程的状态是new,call方法可能还没执行完,准备挂起线程
else if (q == null) {
//封装waitNode存放当前线程
q = new WaitNode();
}
// 4. 加入等待队列(第一次循环可能未进来,第二次进来)
else if (!queued) {
//如果waitNode还没有排在waiters中,现在就排进来(头插法的效果)
queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
q.next = waiters, q);
}
// 5. 阻塞等待
else if (timed) {
//get(time,unit)挂起线程的方式
//计算挂起时间
nanos = deadline - System.nanoTime();
//挂起的时间是否<=0,移除
if (nanos <= 0L) {
removeWaiter(q);
return state;
}
//指定挂起时间,线程挂起
LockSupport.parkNanos(this, nanos);
}
//get()挂起线程的方式
else {
LockSupport.park(this);
}
}
}

线程挂起后,如果任务执行完成,由finishCompletion唤醒线程
java
//任务状态已经变为normal,做一些后续处理
private void finishCompletion() {
// assert state > COMPLETING;
for (WaitNode q; (q = waiters) != null;) {
//拿到第一个节点后,直接用cas的方式,将其设置为null
if (WAITERS.weakCompareAndSet(this, q, null)) {
for (;;) {
//基于q拿到线程信息
Thread t = q.thread;
//线程不为null
if (t != null) {
//将waitingode的thread设置为null
q.thread = null;
//唤醒这个线程
LockSupport.unpark(t);
}
//完后遍历,接着唤醒
WaitNode next = q.next;
if (next == null)
break;
q.next = null; // unlink to help gc
//指向next的waitNode
q = next;
}
break;
}
}
//扩展方法,可以自己实现
done();
//任务处理结束,完成
callable = null; // to reduce footprint
}
拿到返回结果的处理
java
//任务结束
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
//异常返回 report
//取消任务 report
//中断任务 awaitDone
四、CompletableFuture介绍
FutureTask存在问题
-
问题1-主线程阻塞-get:FutureTask获取线程执行的结果前,主线程需要通过get方法,一直阻塞着等待,直到线程执行完call方法后,才能拿到对应数据。
-
问题2-同步-isDone:如果不通过get去挂起线程,通过while循环,不停的去判断任务的执行状态是否结束,结束后,再拿结果。如果任务长时间没执行完毕,CPU会一直调度查看任务状态的方法,会浪费CPU资源。
FutureTask是一个同步非阻塞处理任务的方式。
需要一个异步非阻塞处理任务的方式。CompletableFuture再一定程度上提供了各种异步非阻塞的处理方案,并且提供响应式编程,代码编写上,效果更佳(更漂亮)。
CompletableFuture是JDK1.8,也实现了FutureTask,直接使用CompletableFuture即可。
提供了非常丰富的函数去执行各种异步操作。
初识CompletableFuture应用:
java
//正常返回 report
//异常返回 report
//取消任务 report
//中断任务 awaitDone
/**
* 打印:两个任务同时执行,但是最终需要合并
* ForkJoinPool.commonPool-worker-5:执行二个任务
* ForkJoinPool.commonPool-worker-19:执行一个任务
* 执行第一个任务结束执行第二个任务结束
* @return
*/
private static String twoTaskAddJoin() {
return CompletableFuture.supplyAsync(()->{
System.out.println(Thread.currentThread().getName()+":执行一个任务");
return "执行第一个任务结束";
//后一任务异步执行,
}).thenCombineAsync(CompletableFuture.supplyAsync(()->{
System.out.println(Thread.currentThread().getName()+":执行二个任务");
return "执行第二个任务结束";
}),(result1,result2)->{
return result1+result2;
}).join();
}
/**
* 打印:第二个任务可以同步执行,但是某一步需要依赖前一任务
* ForkJoinPool.commonPool-worker-19:执行一个任务
* ForkJoinPool.commonPool-worker-19:执行二个任务
* 执行第一个任务结束
* 执行第二个任务结束
* @return
*/
private static String twoAsyncTaskJoin() {
//异步执行一个功能
return CompletableFuture.supplyAsync(()->{
System.out.println(Thread.currentThread().getName()+":执行一个任务");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "执行第一个任务结束";
//后一任务异步执行,
}).thenApplyAsync(result->{
System.out.println(Thread.currentThread().getName()+":执行二个任务");
//第一个任务执行结束
System.out.println(result);
return "执行第二个任务结束";
}).join();
}
/**
* 打印:第二任务依赖前一任务
* main线程获取结果
* ForkJoinPool.commonPool-worker-19:执行一个任务
* 执行第一个任务结束
* 执行第二个任务结束
*/
private static void twoTask() {
//异步执行一个功能
CompletableFuture<String> future= CompletableFuture.supplyAsync(()->{
System.out.println(Thread.currentThread().getName()+":执行一个任务");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "执行第一个任务结束";
}).thenApply(result->{
System.out.println(result);
return "执行第二个任务结束";
});
System.out.println("main线程获取结果");
System.out.println(future.join());
}
总结下:
FutureTask是同步非阻塞。
FutureTask会配合Callable执行有返回结果的任务。
如果需要拿到返回结果,需要执行get方法,获取最终结果。
因为无法实现异步非阻塞,CompletableFuture可以实现异步非阻塞的效果再带有返回结果的线程执行完毕后,提供一个回调。
4.1 CompletableFuture的常见方法
CompletableFuture最重要的就是解决了异步回调的问题
CompletableFuture就是执行了一个异步任务,异步任务可以有返回结果,也可以没有返回结果。
CompletableFuture提供了两个最基本运行的基本方法。
函数式编程中,三个最核心的接口:
java
Supplier-生产者,没有入参,但是有返回结果
Consumer-消费者,有入参,但是没有返回结果
Function-函数,有入参,并且有返回结果
supplyAsync(Supplier++Supplier)
异步执行任务,有返回结果
runAsync(Runnable runnable)
异步执行任务,没有返回结果
在不指定线程池的前提下,这两个异步任务都是交给ForkJoinPool去执行的。
而ForkJoinPool内部是守护线程,守护线程在主线程执行完后就不干活了。++
但是只只用这两个方法,无法实现异步回调的。
需要在当前任务执行完毕后,拿着返回结果或者不拿返回结果,继续去执行后续任务操作的话,需要基于其他方法去实现。
这里的方法有个特点,都是在前置任务执行完后,再去执行当前任务
java
/**
*两个参数:第一个是上一个返回结果,第二个是下一个返回结果
*/
thenApply( Function<? super T,? extends U> fn)
/**
* demo如下
*/
CompletableFuture<Integer> task = CompletableFuture.supplyAsync(() -> {
System.out.println("前置任务处理");
return null;
}).thenApply(result->{
System.out.println("上一个任务返回结果:"+result);
System.out.println("后续任务处理");
return 123;
});
task.join();
等待前一个任务处理结束后,拿着前置任务的返回结果,再做处理,并且返回当前任务。
thenApplyAsync 方法解析
java
thenApplyAsync( Function<? super T,? extends U> fn)
实例如下:
CompletableFuture<Integer> task = CompletableFuture.supplyAsync(() -> {
sout("前置任务处理");
return null;
},executorService).thenApplyAsync(result->{
sout("上一个任务返回结果:"+result);
sout("后续任务处理");
return 123;
},executorService);
task.join();
跟上面的套路一致,但是再执行后续任务时,采用全新的线程执行。
所以后续看的任务都有一个特点,大部分方法一共有三种方法重载。
不带Async,带Async和带Async还可以传入线程池的套路
thenAccept方法解析
java
等待前一个任务处理结束后,拿着前置任务的返回结果,再做处理,当前处理没有返回结果
thenAccept(Consumer<? super T> action)
thenRun方法解析
//等待前一个任务处理结束后,再做处理,不接收前置任务结果,也不返回结果
thenRun(Runnable action,线程池)
其次还有可以执行相对复杂的处理,在前一个任务执行的同时,执行后续任务,等待前置任务和后置任务都搞定之后,再执行最终任务。
thenCombine
java
thenCombine(completionStage,Function<preResult,nextResult,afterResult>)
可以让任务1和任务2一起执行,等待任务1和任务2全部搞定,获取前两个任务的结果执行最终处理,最终处理也可以返回结果
执行案例如下:
java
CompletableFuture<Integer> task=CompletableFuture.supplyAsync(() -> {
sout("前置任务处理");
return 1;
},executorService).thenCombine(CompletableFuture.supplyAsync(() -> {
sout("后续任务处理");
return 2;
}),(result1,result2)->{
sout("最终任务处理:"+result1+" "+result2);
return result1+result2;
});
System.out.println(task.get());
thenAcceptBoth方法分析
java
//让前置任务和后续任务同时执行,都执行完毕后,拿到两个任务的结果,再做后续处理,但是没有返回结果
thenAcceptBoth(completionStage,Consumer<prevResult,nextResult>)
runAfterBoth方法分析
java
//让前置任务和后续任务同时执行,都执行完毕后,再做后续处理
runAfterBoth(completionStage,Runnable)
runAfterBothAsync(completionStage,Runnable,线程池)
后面还提供了可以让两个任务一起执行,但是有一个任务结束,有返回结果后,就做最终处理。
applyToEither分析
java
//前面两个任务同时执行,有一个任务执行完,获取返回结果,做最终处理,再返回结果
applyToEither(completionStage,function<firstResult,AfterResult>)
//前面两个任务同时执行,有一个任务执行完,获取返回结果,做最终处理
applyToEither(completionStage,function<firstResult>)
//前面两个任务同时执行,有一个任务执行完,直接做最终处理
applyToEither(completionStage,Runnable)
后面还提供了等到前置任务处理完,再做后续处理,后续处理返回的结果为CompletionStage
java
//连接两个任务,前置处理完,执行后续,后续可以拿到前置处理任务的结果,并且做处理,最终返回的是CompletionStage
thenCompose(function<prevResult,completionStage>)
//案例如下:不过一般性用thenApply就足够了
var task=CompletableFuture.supplyAsync(() -> {
return 1;
}).thenCompose(result->{
return CompletableFuture.supplyAsync(() -> {
sout("前置任务处理");
return null;
});
});
task.join();
最后还有处理异常的各种方式
exceptionally分析
java
exceptionally(function<Throwable,currResult>)
//案例
var task=CompletableFuture.supplyAsync(() -> {
return 1;
}).thenCompose(result->{
return CompletableFuture.supplyAsync(() -> {
sout("前置任务处理");
return null;
});
}).exceptionally(ex->{
sout("异常处理:"+ex.getMessage());
return 0;
});
task.join();
whenComplete分析
java
//可以拿到上一个任务的返回结果和异常,但是当前处理没有返回结果,无法影响最终让任务的结果内容,带有Async操作
whenComplete(consumer<preResult,throwabl>))
//可以拿到上一个任务的返回结果和异常,当前处理有返回结果信息
handle(consumer<preResult,throwabl,currResult>))
4.1 CompletableFuture的应用
例子1.小明要回家吃饭,妻子做饭,小明看电视,等到妻子做完,小明吃饭。
java
sout("小明回家吃饭");
var task=CompletableFuture.supplyAsync(()->{
sout("老婆做饭");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return "水煮鱼";
});
sout("小明看电视");
System.out.println("小明吃饭:"+task.join());
例子2.小明回家吃饭,妻子炒菜,儿子焖饭,小明看电视,等到菜和饭做好,女儿端饭,小明吃饭。
java
sout("小明回家吃饭");
var task=CompletableFuture.supplyAsync(()->{
sout("老婆做菜");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return "水煮鱼";
},executorService).thenCombine(CompletableFuture.supplyAsync(()->{
sout("孩子做饭");
try {
Thread.sleep(2500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return "大米饭";
},executorService),(food,rice)->{
sout("女儿端上:"+food+"和"+rice);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return "饭菜好了";
});
sout("小明看电视");
System.out.println("小明吃饭:"+task.join());
五、使用场景分析
5.1 三个以上表并行查询如何处理
解决方案一、启用线程处理方式
通过Thread+FutureTask方式进行获取处理
优势:可以通过多线程获取到对应数据
劣势:代码复杂度高,且线程复用性差
java
FutureTask<String> task1 = new FutureTask<>(new Callable<String>() {
@Override
public String call() throws Exception {
Thread.sleep(2000);
return "1";
}
});
Thread t1=new Thread(task1);
t1.start();
FutureTask<String> task2 = new FutureTask<>(new Callable<String>() {
@Override
public String call() throws Exception {
Thread.sleep(2000);
return "2";
}
});
Thread t2=new Thread(task2);
t2.start();
System.out.println("结果:"+task1.get()+"task2:"+task2.get());
解决方案二、线程池并行处理
通过线程池的submit方式,进行并行查询处理。
原理:此类方法是同步非阻塞方式运行,即方法需要显性调用才行,不可进行隐形调用
优势:支持数据的并行处理 ,相对于线程方式,提高了线程复用效率
劣势:仅能支持线程池并行处理,无法支持任务间前后依赖依赖,使用场景有限
java
ExecutorService executorService = Executors.newFixedThreadPool(10);
//多线程同时查询三个不同表
//submit 查询数据返回实例
Future<Object> submit1 = executorService.submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
Thread.sleep(2000);
return "1";
}
}
);
Future<Object> submit2 = executorService.submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
Thread.sleep(2000);
return "2";
}
}
);
Future<Object> submit3 = executorService.submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
Thread.sleep(2000);
return "3";
}
}
);
System.out.println("结果sub1:"+submit1.get()+"sub2:"+submit2.get()+"sub3:"+submit3.get());
解决方案三、List
通过定义List进行并行处理
原理:此类方法是利用List支持并行流进行处理,默认将线程提交给了forkjoin线程池
ForkJoinPool.commonPool()
优势:支持数据的并行处理 ,代码简单方便
劣势:线程池默认,无法进行自定义配置;且数值需要通过对象进行传递才行
java
private static void listRuabbleTest() {
List<Runnable> list=new ArrayList<>();
StringBuilder t1=new StringBuilder();
StringBuilder t2=new StringBuilder();
list.add(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
t1.append("1");
}
});
list.add(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
t2.append("1");
}
});
list.parallelStream().forEach(Runnable::run);
System.out.println("结果:"+t1.toString()+"t2:"+t2.toString());
}
解决方案四、CompletableFuture方案
通过CompeletableFuture方案,通过并行获取拿到结果
原理:采用异步非阻塞方式,
优势:并行获取效率较高,且支持自定义线程池方式进行处理
java
private static void completableFutureTest() {
//默认forkjoin线程池
List<String> list=new CopyOnWriteArrayList<>();
CompletableFuture.runAsync(()->{
list.add("1");
}).runAsync(()->{
list.add("2");
}).runAsync(()->{
list.add("3");
}).join();
System.out.println("结果:"+list.stream().collect(Collectors.joining()));
}
//自定义线程池方式
ExecutorService executorService = Executors.newFixedThreadPool(10);
CompletableFuture.allOf(CompletableFuture.runAsync(()->{
list.add("1");
},executorService),CompletableFuture.runAsync(()->{
list.add("2");
},executorService),CompletableFuture.runAsync(()->{
list.add("3");
},executorService)).join();
System.out.println("结果:"+list.stream().collect(Collectors.joining()));
5.2 任务串并联多种方式

如图所示,处理思路先从主干再到分支。
步骤1:任务1主干-任务2主干
步骤2:任务2->步骤3分支并行
步骤3:任务2->任务4分支串行
步骤4:任务4->任务5 6 7 8,分支并行
java
CompletableFuture<String> task1 = CompletableFuture.supplyAsync(()->{
try {
System.out.println("开始执行任务1");
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("任务1执行完成");
return "任务一完成";
}).thenApply(s->{
System.out.println("开始执行任务2");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("任务2执行完成");
return s+"--任务二完成";
}).thenApplyAsync(s->{
System.out.println("任务3执行完成");
return s+"--任务三完成";
}).thenApplyAsync(s->{
System.out.println("任务四完成");
var tt1=CompletableFuture.allOf(CompletableFuture.supplyAsync(()->{
System.out.println("开始执行任务5");
return "任务五完成";
}).thenApplyAsync(s1->{
System.out.println("任务6执行完成");
return s1+"--任务6完成";
})
.thenApplyAsync(s1->{
System.out.println("任务7执行完成");
return s1+"--任务7完成";
})
.thenApplyAsync(s1->{
System.out.println("任务8执行完成");
return s1+"--任务8完成";
})
).join();
return s+"--"+tt1;
});
System.out.println(task1.join());
5.3 需要等待多个任务执行完如何处理
使用场景:此处是一个算法exe执行监控,需要满足算法exe执行完成,且文件生成完成后才将文件结果合并并发送消息信息。
java
private static void completableFutureTest1() {
//等待处理完成
CompletableFuture.supplyAsync(()->{
System.out.println("执行算法任务");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("算法任务完成");
return "任务完成";
}).thenCombine(CompletableFuture.supplyAsync(()->{
System.out.println("检查文件信息");
return "";
}), (s, s2) -> {
System.out.println("算法执行正常,结束执行");
return "完成";
}).exceptionally(
throwable -> {
System.out.println("算法执行异常");
return "异常处理完成";
}
).join();
System.out.println("算法执行结束,发送结束标识位信息");
}