FutureTask
我们在使用线程池时, 发现线程池除了execute方法, 还有一个submit方法
, submit方法会返回一个Future类型, 然后能够使用这个返回值的get方法, 得到异步任务的返回值
, 当然, 这时候传递给线程池的对象应该是一个Callable的对象.
ExecutorService executorService = Executors.newFixedThreadPool(2);
Future<?> submit = executorService.submit(() -> {
System.out.println(123);
TimeUnit.SECONDS.sleep(3);
return "hello";
});
System.out.println(submit.get());
上面这段代码将会阻塞在submit.get()处3秒, 直到submit提交的任务完成之后, get()方法才会放行, 并且get()方法将会得到submit提交的任务的返回值, 也就是"hello".
Runnable和Callable
submit方法与execute方法很类似, 只是传递的对象不同, execute需要一个Runnable, 而submit需要一个Callable
:
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
Callable是一个带泛型的类, 返回值的类型就是其泛型
, 通过看代码可以知道, Runnable和Callable主要有如下几种不同点:
1. Callable有泛型, 并且call方法有返回值, 返回值类型是其泛型.
2. Callable的call方法可以抛出异常, 而Runnable的run方法只能人为自己在所实现的run方法中自己捕捉异常, 或者抛出RuntimeException.
因此, Callable的作用是比Runnable要更加强大一点.
Future接口
submit方法返回的是一个Future类型, 那么我们首先看看Future这个接口到底是什么样的:
public interface Future<V> {
// 取消当前任务
boolean cancel(boolean mayInterruptIfRunning);
// 当前任务是否被取消
boolean isCancelled();
// 当前任务是否已完成
boolean isDone();
// 拿到当前任务的处理结果, 一直阻塞
V get() throws InterruptedException, ExecutionException;
// 拿到当前任务的处理结果, 超过超时时间则抛异常
V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
}
Future接口在jdk中提供了如下几个继承/实现接口/类, 当然并不是所有都是常用的, 实际上比较常用的也就是FutureTask, ScheduledFutureTask, CompletableFuture
这几个类.
而在线程池的submit方法中, 返回的类型就是FutureTask类型.
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
// 将从外部接收的Callable类型直接传递给FutureTask的构造函数, 得到一个FutureTask对象
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
return new FutureTask<T>(callable);
}
在使用submit方法的时候, 我们自己构造了一个Callable对象传递进去, 这个Callable对象直接被拿去创建一个FutureTask对象, 然后execute(task)去执行这个FutureTask, 从这里我们就可以看出来, FutureTask肯定是实现了Runnable的
.
FutureTask实现了RunnableFuture接口, 而RunnableFuture接口既继承了Runnable也继承了Future接口, 因此FutureTask实际上就是既实现Future接口, 也要实现Runnable接口
.
FutureTask原理
FutureTask简述
想要获得一个FutureTask, 首先要提供一个Callable对象, FutureTask提供了一个单参数的构造函数, 支持传递一个Callable对象, 内部还设置了一个状态属性state, 初始化为NEW(int类型, 值为0)
, 并且这个state是一个volatile的, 以供其它同时调用get()方法的线程第一时间知道任务完成了
.
// volatile的state状态属性, 使得任务执行完之后能让其它get阻塞的线程第一时间知道当前任务已经完成了
private volatile int state;
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
// FutureTask提供的几个状态枚举值
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;
我们知道, 在线程池中, execute方法是一个异步的, 直接将Runnable交给线程去执行, 自己不会阻塞
, 因此在submit方法中的execute(task)这行, 也仅仅就是将这个FutureTask当做一个Runnable, 使用线程池中的线程去执行它的run方法, 然后就直接返回这个futureTask.
submit返回后, 我们会调用这个返回的futureTask的get方法, 如果此时任务还没执行完, 这里将会阻塞, 直到任务执行完才会放行, 并且得到Callable的返回值. 因此, 可以确定, 这个阻塞的原因, 就发生在FutureTask的run()方法和get()方法中了
.
get方法
public V get() throws InterruptedException, ExecutionException {
int s = state;
// 如果任务没完成, 将会进入awaitDone方法
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}
上面我们得知, 如果任务完成了, 这时候将会直接进入report(s)方法中, 返回任务处理结果, 而如果任务没有完成, state的状态将会是NEW, 也就是0, 这时是小于COMPLETING(1)的, 将会进入awaitDone方法
. get方法的阻塞正是阻塞在awaitDone方法中
.
awaitDone方法
首先我们只看主线, 不看超时的情况, 也不看打断情况, 在无超时的get()方法中, 传递给awaitDone方法的两个参数分别为false和0, 因此awaitDone方法简化如下:
private int awaitDone(boolean timed, long nanos) throws InterruptedException {
WaitNode q = null;
boolean queued = false;
for (;;) {
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();
else if (!queued)
// 这里的写法很精髓, q.next = waiters, 既保证了传入的第三个参数是waiters的原始值
// 还把waiters属性赋值给了q的next属性, 因此这是个头插法
queued = UNSAFE.compareAndSwapObject(this, waitersOffset, q.next = waiters, q);
else
LockSupport.park(this);
}
}
首先这是一个for的死循环
, 我们假设调用get()方法的此时, 线程池的execute还没执行FutureTask的run方法的地方(或者说还没执行到更改state状态的地方), 那么这时候int s = state得到的s是0, 肯定小于COMPLETING
, 于是进入了q == null的判断, 此时q肯定是null, 于是就调用WaitNode的构造方法创建WaitNode对象, 创建WaitNode的时候, 将WaitNode的thread设置为当前执行get()方法的线程
. WaitNode类的代码如下所示:
static final class WaitNode {
volatile Thread thread;
volatile WaitNode next;
WaitNode() {
thread = Thread.currentThread();
}
}
于是q中就包含了当前调用get()方法的线程, 是为thread属性. 初始化完毕q之后, 回到循环入口处
, 继续往下.
接下来又进入!queued
的判断中. queued的含义是q是否入队
, 第一次进入循环中, queued是false, 表示未入队, 于是就要入队了. 入队使用了cas操作, 因为可能有多个线程调用了get()方法
, 为了线程安全地入队, 就必须使用cas+死循环保证线程安全. 在这个cas方法中, 参数传递了this, waitersOffset, 实际上waitersOffset就是FutureTask类中waiters属性的偏移量
, 这个操作结束后, 将会线程安全地把q这个节点加入到waiters属性中. 如果多个线程同时调用get()方法导致某次的cas失败, 将会再次回到for循环的入口处, 继续尝试cas, 直到所有调用get()方法的线程创建的WaitNode节点都加入到waiters属性中为止
.
入队成功后, queued被置为true, 于是又回到循环的入口处
, 继续往下.
这次将会进入到最终的else块中, 这时候将直接把调用get()方法的线程park. 于是get方法就阻塞在这里了
.
run方法
同样的规矩, 不看异常, 不看中断, 只看主线, 将run方法简化成如下代码:
public void run() {
// FutureTask只能执行一次
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 {
result = c.call();
ran = true;
} catch (Throwable ex) {
}
if (ran)
set(result);
}
} finally {
}
}
run方法很简单, 直接调用传递过来的Callable对象的call()方法, 得到result, 如果没有出异常, ran变量将被置为true, 然后进入if(ran)的判断中, 判断成立, 调用set(result)方法, 传入call方法的返回值
.
set方法
set方法要完成的任务就是, 让之前调用了get()方法被阻塞的线程全部唤醒, 并且将Callable的call()方法的返回值设置到这个FutureTask对象的属性中
, 我们知道, 一个FutureTask对象是只能执行一次任务的
, 于是这个执行结果也必然只有一个, 那么就可以使用一个属性去保存这个执行结果, 这个属性就是outcome
.
// FutureTask的属性, 用于保存本FutureTask对象的执行结果
private Object outcome;
protected void set(V v) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = v;
UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
finishCompletion();
}
}
首先使用cas修改state的状态值, 从NEW改为COMPLETING
, 然后再将outcome属性赋值为call()方法的返回值之后, 又将state属性设置为NORMAL
, 再调用finishCompletion()方法, 去唤醒之前调用get()方法被阻塞的线程
.
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; // unlink to help gc
q = next;
}
break;
}
}
done();
callable = null; // to reduce footprint
}
该方法的逻辑大概就是, 先拿到waiters的头节点, 再使用cas将waiters属性置为null, 然遍历之前拿到的头结点, 依次唤醒头结点中的线程
, 操作也就是简单的链表操作, 内外for循环都执行完后, 所有之前调用get()方法并且阻塞在awaitDone方法中的线程都将会被解阻塞
.
最后的done()方法是一个空方法
.
截止目前, outcome属性是call()方法的返回值, state=NORMAL(2)
.
awaitDone方法解阻塞
调用get()方法阻塞的线程, 之前是阻塞在最后的else块中的LockSupport.park(this)中
, 被finishCompletion方法唤醒之后, 依然会从park的位置开始执行代码
, 于是又回到for循环的入口处
, 这时候由于state是NORMAL(2)
, 于是就会进入这个if块中.
int s = state;
if (s > COMPLETING) {
if (q != null)
q.thread = null;
return s;
}
判断如果q不为空, 正常情况下肯定不是空, 再将q包含的线程也取消引用, 最终返回状态值s, 正常情况下, 返回的s就是NORMAL, 也就是2.
get()方法返回
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}
awaitDone方法返回了状态s为2, 于是get方法调用report(s), 返回call()方法的返回值.
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);
}
直接返回先前设置的outcome
.
至此, FutureTask的原理就分析完毕了.
FutureTask原理总结
1. get()方法阻塞, 阻塞的是调用get()方法的线程.
2. 每个调用get()方法阻塞的线程, 都被包装在一个WaitNode的对象中, 并且形成一个链表, FutureTask对象的waiters属性保存头结点(头插法, 头结点是最后一个调用get()方法的线程所在节点).
3. 每个调用get()方法阻塞的线程, 都会被阻塞在awaitDone的最后一个else块中: LockSupport.park(this).
4. run()方法内部调用call()方法, 并将call()方法的返回值保存到FutureTask的outcome属性中, 并修改本FutureTask对象的状态为NORMAL.
5. run()方法中调用finishCompletion()方法, 该方法将会遍历waiters属性, unpark每个WaitNode节点的线程.
6. 调用get()方法阻塞的线程被unpark之后, 回到for循环入口, 此时由于状态已经是NORMAL, awaitDone方法将返回, 顺理成章地get()方法也会返回之前赋值的outcome属性.
7. FutureTask生命周期无法后退, 只能执行一次, 执行成功之后, 状态就是确定的了, 不会再改变.
FutureTask局限性
1. 无法并发执行多个任务
: 由于get方法是阻塞的, 因此只能等待所有任务执行完成.
2. 无法组合多个任务
: 如果需要对多个任务的执行结果进行操作, 只能一个一个的来.
3. 没有异常处理
: Future接口中没有对异常进行处理的方法.
4. 使用不够优雅
: 如果在执行完任务想做一些事情, 就必须在调用get()方法的这个方法里面去写.
CompletionService
相比于FutureTask, CompletionService增强了一些性能, 比如有一组任务, 分别从商品服务中获取10个商品的信息, 然后存储. 为了效率, 我们使用FutureTask, 我们就只能分别使用10次get()方法去获取每个商品的返回值. 但这并不是核心问题所在, 问题是我们的get()将会一直阻塞在耗时最长的商品获取的位置, 那么我们开始存储的动作, 一定是要在所有商品都获取成功后才能执行, 这样的话, 效率并不是最优的.
如果有一种方案, 能让先完成任务的FutureTask先去做后续的操作
, 比如10个商品里面, 前面8个商品都获取成功, 不需要管后面2个是否获取成功, 前面8个成功的依次按照完成顺序去执行存储动作, 这样等最慢的那个商品获取成功后, 可能前面的9个商品早已保存成功, 这样对比上面的方式快了9次的存储, 基本属于最优效率了. 那么这样的方式, 就是CompletionService实现的
.
CompletionService概述
CompletionService只有一个实现类
, 叫做ExecutorCompletionService
.
public interface CompletionService<V> {
// 提交一个Callable任务
Future<V> submit(Callable<V> task);
// 提交一个Runnable任务, 返回值就是result
Future<V> submit(Runnable task, V result);
// 如果获取不到元素就阻塞, 可打断
Future<V> take() throws InterruptedException;
// 如果获取不到元素就返回null
Future<V> poll();
// 超过超时时间还获取不到元素就返回null, 超时时间内可以被打断
Future<V> poll(long timeout, TimeUnit unit) throws InterruptedException;
}
通过poll()和take()方法的返回值可以猜出来, 在CompletionService的实现类中, 应该要提供一个阻塞队列, 这个阻塞队列的元素类型是Future类型
.
ExecutorCompletionService应用
下面看看示例
public static void main(String[] args) throws Exception {
ExecutorService executor = Executors.newFixedThreadPool(10);
ExecutorCompletionService<String> service = new ExecutorCompletionService<>(executor);
for (int i = 10; i >= 1; i--) {
int id = i;
service.submit(() -> getProductNameById(id));
}
for (int i = 0; i < 10; i++) {
Future<String> take = service.take();
System.out.println(take.get());
}
}
static String getProductNameById(int id) throws Exception {
TimeUnit.SECONDS.sleep(id);
return "product_" + id;
}
输出结果:
product_1
product_2
product_3
product_4
product_5
product_6
product_7
product_8
product_9
product_10
可以看到, 提交任务的顺序是id从大到小(这样可以更大概率让id更大的先去执行), 然后在获取商品名字的方法中, 根据id休眠id秒, 但是最终打印的结果, 依然是每隔1秒打印一次获取结果, 这样就完成了先执行完查询操作的任务先去执行下一步所做的事情, 而不必要去等待其它任务的执行完成
.
ExecutorCompletionService原理
在创建ExecutorCompletionService的时候, 需要提供一个线程池. 在它的构造函数中, 核心是创建了一个无界队列completionQueue
, 泛型为Future. Future的泛型与创建ExecutorCompletionService时提供的泛型一致.
public ExecutorCompletionService(Executor executor) {
if (executor == null)
throw new NullPointerException();
this.executor = executor;
this.aes = (executor instanceof AbstractExecutorService) ?
(AbstractExecutorService) executor : null;
// 初始化一个无界队列, 队列泛型为Future<V>
this.completionQueue = new LinkedBlockingQueue<Future<V>>();
}
submit方法
public Future<V> submit(Callable<V> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<V> f = newTaskFor(task);
executor.execute(new QueueingFuture(f));
return f;
}
先将传递过来的Callable构造成一个FutureTask, 然后再执行线程池的execute方法, 传入了一个QueueingFuture
, 看看QueueingFuture是什么:
private class QueueingFuture extends FutureTask<Void> {
QueueingFuture(RunnableFuture<V> task) {
super(task, null);
this.task = task;
}
protected void done() { completionQueue.add(task); }
private final Future<V> task;
}
QueueingFuture继承了FutureTask, 封装了一个task, 也就是前面根据传入的Callable创建的那个FutureTask
. 重写了done()方法, 前面在分析FutureTask的时候, 我们看到过, 在FutureTask中, done()方法是一个空方法, 在执行完唤醒所有阻塞在get()方法的线程之后会调用. 但是在QueueingFuture中, 重写了它, 内容就是往无界阻塞队列中填入执行完的FutureTask
.
其它内容与FutureTask基本一致, 只是有一些小出入, 在构造QueueingFuture的时候, 使用了super(Runnable, V)这个构造函数, 但是其实也没有什么难以理解的, 这只不过是一个套娃而已, 对于FutureTask来说, 只要能执行它的run()方法, 就是完成了这个FutureTask的任务了
.
对于FutureTask的执行过程, 前面部分已经详细介绍了, 这里就是多了个done()方法, 在唤醒所有阻塞线程之后, 将已经执行完成的FutureTask加入到无界阻塞队列中
.
take方法
public Future<V> take() throws InterruptedException {
return completionQueue.take();
}
在我们的应用代码的末尾, 使用for循环不断地从这个无界阻塞队列中获取元素, 这些元素就是已经完成了的FutureTask. 获取到FutureTask之后, 再调用get()获取返回值, 这时候调用get()方法一定不会阻塞, 因为FutureTask已经完成了.
CompletionService总结
1. CompletionService在构造时需要传入一个线程池, 这样能够自己定义, 降低任务执行风险
.
2. CompletionService在异步执行任务时, 借助了阻塞队列, 控制任务的有序性, 避免无用的等待
.
CompletableFuture
前面介绍的FutureTask和CompletionService, 都只是在处理简单的任务的时候, 表现比较好, 但是涉及到多任务合并, 多任务互相依赖, 多任务串行等任务复杂编排的情况, 这样就只能手动控制了. 而CompletableFuture则就是为了解决这个问题而出现的.
CompletableFuture是Future接口的拓展, CompletableFuture实现了Future接口, 并在此基础上完善了Future的不足之处, 实现了对任务的编排能力, 借助CompletableFuture, 我们可以轻松的进行对任务的编排
, 而不需要像在使用CountDownLatch等工具类那样还要进行大量的其它操作.
由于所实现的CompletionStage方法实在太多, 就不贴出来了, CompletableFuture为了实现任务编排, api众多, 只要能够使用就可以了, 对于Future原理上的研究, 我认为只要能够理解FutureTask的原理就可以了, 至于CompletableFuture, 核心的几个api能够使用就行了. 下面将介绍几个CompletableFuture的核心api.
执行任务runAsync和supplyAsync
runAsync是执行一个不带返回值的异步任务, 返回值是一个泛型为Void的CompletableFuture
.
supplyAsync是执行一个带返回值的任务, 返回值是一个泛型为Supplier的泛型一样的CompletableFuture
.
public static CompletableFuture<Void> runAsync(Runnable runnable)
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)
runAsync需要提供一个runnable, 那么它真正执行的任务的逻辑就是Runnable的run方法的逻辑.
supplyAsync需要提供一个Supplier, 那么它真正执行的任务的逻辑就是Supplier的get方法的逻辑, 并且Future中的包装结果就是get方法的返回结果.
两种方法都支持传入一个线程池exector, 如果不传递线程池, 则使用默认的一个公共线程池
:
// useCommonPool默认是true, 默认取commonPool, 单例模式
private static final Executor asyncPool = useCommonPool ?
ForkJoinPool.commonPool() : new ThreadPerTaskExecutor();
get()和join()
runAsync和supplyAsync都是异步方法, 执行后不会等待返回结果, 因此这样肯定不行, CompletableFuture提供了join方法和get方法, 用来等待异步任务的执行结束
. 示例如下:
public static void main(String[] args) {
String res = null;
try {
// get()方法会抛出InterruptedException和ExecutionException异常
res = CompletableFuture.supplyAsync(() -> "hello").get();
} catch (Exception e) {
}
System.out.println(res);
// join方法不会抛出异常, 所有异常均为非检查的异常
String res2 = CompletableFuture.supplyAsync(() -> "hello2").join();
System.out.println(res2);
CompletableFuture.runAsync(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
}
System.out.println("休眠两秒执行");
});// 不加get()/join()将不会等待执行结束, 主线程就停止了运行
}
输出:
hello
hello2
使用get()或者join()将会得到异步任务执行的结果, 泛型类型保证了结果的类型不需要强制转换
.
whenComplete
whenComplete支持3种模式, 代码如下:
public CompletableFuture<T> whenComplete(BiConsumer<? super T, ? super Throwable> action)
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T, ? super Throwable> action)
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T, ? super Throwable> action, Executor executor)
一种是同步执行, 也就是使用前面使用的同一个线程执行业务逻辑, 另一种是异步执行, 支持传入一个线程池, 如果不传入线程池, 则使用默认的common线程池
.
whenComplete
whenComplete作用于前一个任务执行完成后, whenComplete中的逻辑会执行
, whenComplete支持同步和async两种方式, whenComplete同步就是使用主线程执行whenComplete中的业务
.
而whenCompleteAsync则支持传递一个线程池executor, 如果不传入, 则使用默认的common线程池执行whenComplete中的业务逻辑, 如果传入, 就使用传入的自定义线程池执行
.
后面很多api都带有async模式, 意义是一样的
, 后面将不再赘述, 直接提供api示例.
public static void main(String[] args) {
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread() + " 执行有返回值的异步任务");
return "hello world";
// whenComplete 同步执行, 使用主线程执行
}, Executors.newFixedThreadPool(2));
System.out.println();
future.whenComplete((s, throwable) -> System.out.println(Thread.currentThread() + " " + s + " when complete")).join();
}
输出:
Thread[pool-1-thread-1,5,main] 执行有返回值的异步任务
Thread[main,5,main] hello world when complete
whenCompleteAsync
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(2);
CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread() + " 执行有返回值的异步任务");
return "hello world";
// whenCompleteAsync, 如果提供了线程池, 使用提供的线程池执行任务
// 如果没提供, 则使用默认的common线程池
}, executorService).whenCompleteAsync(
(s, throwable) -> System.out.println(Thread.currentThread() + " " + s + " when complete"),
executorService
).join();
}
输出:
Thread[pool-1-thread-1,5,main] 执行有返回值的异步任务
Thread[pool-1-thread-2,5,main] hello world when complete
thenApply
thenApply
public static void main(String[] args) {
User user = CompletableFuture.supplyAsync(() -> "mike")
.thenApply(
s -> new User(s, 22, Thread.currentThread().toString())
).join();
System.out.println(String.format("name: %s, age: %d, thread: %s", user.name, user.age, user.thread));
}
输出:
name: mike, age: 22, thread: Thread[main,5,main]
thenApplyAsync
public static void main(String[] args) {
User user = CompletableFuture.supplyAsync(() -> "mike")
.thenApplyAsync(
s -> new User(s, 22, Thread.currentThread().toString()),
Executors.newFixedThreadPool(2)
).join();
System.out.println(String.format("name: %s, age: %d, thread: %s", user.name, user.age, user.thread));
}
输出:
name: mike, age: 22, thread: Thread[pool-1-thread-1,5,main]
thenCompose
thenCompose需要提供一个CompletableFuture, 业务逻辑也是在这个supplyAsync中执行的, 因此本身就不可能使用主线程执行业务, 但是提交任务的线程却是主线程
. 根据我的研究, 我也就只发现了thenCompose和thenApply就这一点不同点, 其它的感觉差不多.
thenComposeAsync支持传入一个线程池, 那么提交任务的线程就不会再是主线程了, 而是传入的这个线程池中的线程, 如果没传入, 则使用默认的common线程池
.
thenCompose
public static void main(String[] args) {
User user = CompletableFuture.supplyAsync(() -> "hello")
.thenCompose(
s -> {
System.out.println(Thread.currentThread());
return CompletableFuture.supplyAsync(() -> new User(s, 22, Thread.currentThread().toString()));
}
).join();
System.out.println(String.format("name: %s, age: %d, thread: %s", user.name, user.age, user.thread));
}
输出:
Thread[main,5,main]
name: hello, age: 22, thread: Thread[ForkJoinPool.commonPool-worker-9,5,main]
thenComposeAsync
public static void main(String[] args) {
User user = CompletableFuture.supplyAsync(() -> "hello")
.thenComposeAsync(
s -> {
System.out.println(Thread.currentThread());
return CompletableFuture.supplyAsync(() -> new User(s, 22, Thread.currentThread().toString()));
},
Executors.newFixedThreadPool(2)
).join();
System.out.println(String.format("name: %s, age: %d, thread: %s", user.name, user.age, user.thread));
}
输出:
Thread[pool-1-thread-1,5,main]
name: hello, age: 22, thread: Thread[ForkJoinPool.commonPool-worker-9,5,main]
thenCombine
thenCombine
public static void main(String[] args) {
User user = CompletableFuture.supplyAsync(() -> "mike")
.thenCombine(
CompletableFuture.supplyAsync(() -> 22),
(s, integer) -> new User(s, integer, Thread.currentThread().toString())
).join();
System.out.println(String.format("name: %s, age: %d, thread: %s", user.name, user.age, user.thread));
}
输出:
name: mike, age: 22, thread: Thread[main,5,main]
thenCombineAsync
public static void main(String[] args) {
User user = CompletableFuture.supplyAsync(() -> "mike")
.thenCombineAsync(
CompletableFuture.supplyAsync(() -> 22),
(s, integer) -> new User(s, integer, Thread.currentThread().toString()),
Executors.newFixedThreadPool(2)
).join();
System.out.println(String.format("name: %s, age: %d, thread: %s", user.name, user.age, user.thread));
}
输出:
name: mike, age: 22, thread: Thread[pool-1-thread-1,5,main]
thenAccept
对单个结果进行消费, 不具有返回值, 但是可以使用前面的任务的处理结果
.
thenAccept
public static void main(String[] args) {
User[] userx = new User[]{null};
CompletableFuture.supplyAsync(() -> "hello")
.thenAccept(
s -> userx[0] = new User(s, 22, Thread.currentThread().toString())
).join();
User user = userx[0];
System.out.println(String.format("name: %s, age: %d, thread: %s", user.name, user.age, user.thread));
}
输出:
name: hello, age: 22, thread: Thread[main,5,main]
thenAcceptAsync
public static void main(String[] args) {
User[] userx = new User[]{null};
CompletableFuture.supplyAsync(() -> "hello")
.thenAcceptAsync(
s -> userx[0] = new User(s, 22, Thread.currentThread().toString()),
Executors.newFixedThreadPool(2)
).join();
User user = userx[0];
System.out.println(String.format("name: %s, age: %d, thread: %s", user.name, user.age, user.thread));
}
输出:
name: hello, age: 22, thread: Thread[pool-1-thread-1,5,main]
thenAcceptBoth
对两个结果进行消费, 不具有返回值, 但是要使用两个异步任务的结果
.
thenAcceptBoth
public static void main(String[] args) {
User[] userx = new User[]{null};
CompletableFuture.supplyAsync(() -> "hello")
.thenAcceptBoth(
CompletableFuture.supplyAsync(() -> 22),
(s, integer) -> userx[0] = new User(s, integer, Thread.currentThread().toString())
).join();
User user = userx[0];
System.out.println(String.format("name: %s, age: %d, thread: %s", user.name, user.age, user.thread));
}
输出:
name: hello, age: 22, thread: Thread[main,5,main]
thenAcceptBothAsync
public static void main(String[] args) {
User[] userx = new User[]{null};
CompletableFuture.supplyAsync(() -> "hello")
.thenAcceptBothAsync(
CompletableFuture.supplyAsync(() -> 22),
(s, integer) -> userx[0] = new User(s, integer, Thread.currentThread().toString()),
Executors.newFixedThreadPool(2)
).join();
User user = userx[0];
System.out.println(String.format("name: %s, age: %d, thread: %s", user.name, user.age, user.thread));
}
输出:
name: hello, age: 22, thread: Thread[pool-1-thread-1,5,main]
thenRun
某个任务执行完之后执行一个操作, 这个操作不依赖那个任务的返回值, 并且这个操作也不会返回任何东西. 传入的是一个Runnable对象
.
thenRun
public static void main(String[] args) {
String[] msgs = new String[]{null};
CompletableFuture.supplyAsync(() -> "hello")
.thenRun(
() -> msgs[0] = Thread.currentThread() + " - run"
).join();
System.out.println(msgs[0]);
}
输出:
Thread[main,5,main] - run
thenRunAsync
public static void main(String[] args) {
String[] msgs = new String[]{null};
CompletableFuture.supplyAsync(() -> "hello")
.thenRunAsync(
() -> msgs[0] = Thread.currentThread() + " - run",
Executors.newFixedThreadPool(2)
).join();
System.out.println(msgs[0]);
}
输出:
Thread[pool-1-thread-1,5,main] - run
AllOf
Allof是一个静态方法:
public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs)
这个方法需要传递多个CompletableFuture, 顾名思义, 就是要等待所有传入的CompletableFuture对象的任务都执行完, 用法是得到allOf的返回值后, 调用get()/join()方法, 就可以等待所有传入的异步任务执行完成了
.
示例如下: 本示例中传入了3个CompletableFuture, 每个CompletableFuture都是先生产出一个User对象, 然后再使用thenAccept将前面的任务得到的User对象放入users列表中.
调用join()方法对allOf阻塞
.
最终解阻塞后, 得到users列表中正是这3个User对象.
get()/join()方法返回的是Void
.
public static void main(String[] args) {
List<User> users = new ArrayList<>();
ExecutorService executorService = Executors.newFixedThreadPool(10);
CompletableFuture.allOf(
CompletableFuture.supplyAsync(() -> new User("mike", 21, Thread.currentThread().toString()), executorService).thenAccept(user -> users.add(user)),
CompletableFuture.supplyAsync(() -> new User("john", 23, Thread.currentThread().toString()), executorService).thenAccept(user -> users.add(user)),
CompletableFuture.supplyAsync(() -> new User("may", 32, Thread.currentThread().toString()), executorService).thenAccept(user -> users.add(user))
).join();
for (int i = 0; i < users.size(); i++) {
User user = users.get(i);
System.out.println(String.format("name: %s, age: %d, thread: %s", user.name, user.age, user.thread));
}
}
输出:
name: mike, age: 21, thread: Thread[pool-1-thread-1,5,main]
name: john, age: 23, thread: Thread[pool-1-thread-2,5,main]
name: may, age: 32, thread: Thread[pool-1-thread-3,5,main]
AnyOf
anyOf用法与allOf差不多, 也是传入多个CompletableFuture, 使用join()/get()等待传入的异步任务执行完成.
区别在于anyOf只要有一个返回, 就会解阻塞, 并且join()/get()得到的结果是一个Object类型, 正是传入的这些CompletableFuture中执行最快的那个任务的返回值
.
下面代码同样传入3个生产User对象的CompletableFuture, 循环执行5次, 随机得到了不同的User对象.
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
User user = (User) CompletableFuture.anyOf(
CompletableFuture.supplyAsync(() -> new User("john", 23, Thread.currentThread().toString())),
CompletableFuture.supplyAsync(() -> new User("mike", 21, Thread.currentThread().toString())),
CompletableFuture.supplyAsync(() -> new User("may", 32, Thread.currentThread().toString()))
).join();
System.out.println(String.format("name: %s, age: %d, thread: %s", user.name, user.age, user.thread));
}
}
输出:
name: john, age: 23, thread: Thread[ForkJoinPool.commonPool-worker-9,5,main]
name: mike, age: 21, thread: Thread[ForkJoinPool.commonPool-worker-9,5,main]
name: may, age: 32, thread: Thread[ForkJoinPool.commonPool-worker-11,5,main]
name: john, age: 23, thread: Thread[ForkJoinPool.commonPool-worker-9,5,main]
name: mike, age: 21, thread: Thread[ForkJoinPool.commonPool-worker-4,5,main]