多线程的优化
串行和并行
实际上多线程就是将各个串行操作,变成并行操作,最大化利用等待的时间。因此多线程实现时,我们主要要实现的业务逻辑就是分工和协作
多线程实现也一般伴随异步化。那么什么是同步和异步呢?异步中,单线程和多线程又有什么区别呢
同步和异步
假设有两个任务A 和 B
同步:执行A时,只能等待A结束,取得结果,再执行B,即使执行A时资源有空闲,也只能先执行A。
异步:
- 单线程: 先执行A, 过一段时间 t1,不管A是否执行完,也得停下A任务,开始处理B了。
- 多线程:两个线程,一个执行A, 一个执行B
CompletableFuture
的核心优势
- 无需手工维护线程,没有繁琐的手工维护线程的工作,给任务分配线程的工作也不需要我们关注;
- 语义更清晰,例如
f3 = f1.thenCombine(f2, ()->{})
能够清晰地表述“任务 3 要等待任务 1 和任务 2 都完成后才能开始”; - 代码更简练并且专注于业务逻辑,几乎所有代码都是业务逻辑相关的。
创建 CompletableFuture
对象
// 使用默认线程池
static CompletableFuture<Void>runAsync(Runnable runnable)
static <U> CompletableFuture<U>supplyAsync(Supplier<U> supplier)
// 可以指定线程池
static CompletableFuture<Void>runAsync(Runnable runnable, Executor executor)
static <U> CompletableFuture<U>supplyAsync(Supplier<U> supplier, Executor executor)
- 前两个方法使用默认线程池,它们之间的区别是:Runnable 接口的 run() 方法没有返回值,而 Supplier 接口的 get() 方法是有返回值的。
- 后两个方法则可以指定线程池参数,成员变量只多了Executor executor,默认情况下会使用
ForkJoinPool
线程池 - 创建完 CompletableFuture 对象之后,会自动地异步执行
runnable.run()
方法或者supplier.get()
方法
对于一个异步操作,你需要关注两个问题:
- 一个是异步操作什么时候结束,
- 另一个是如何获取异步操作的执行结果
由于CompletableFuture 类实现了 Future 接口,直接通过Future接口就可以解决了
P.S. ForkJoinPool
线程池是一种更智能的线程池,支持Fork/Join的并行计算(类似单机版的MapReduce),利用生产者消费者模型实现,并持“任务窃取“机制
如何理解 CompletionStage
接口
CompletionStage
接口可以清晰地描述任务之间的时序关系,如串行,并行,汇聚(等待两个任务执行都完,才能执行总任务,也可以用CyclicBarrier
实现)
f3 = f1.thenCombine(f2, ()->{})
描述的就是一种汇聚关系,语义很清晰,f3需要等待f1,和 f2执行完成之后才执行
1. 描述串行关系
CompletionStage<R> thenApply(fn);
CompletionStage<R> thenApplyAsync(fn);
CompletionStage<Void> thenAccept(consumer);
CompletionStage<Void> thenAcceptAsync(consumer);
CompletionStage<Void> thenRun(action);
CompletionStage<Void> thenRunAsync(action);
CompletionStage<R> thenCompose(fn);
CompletionStage<R> thenComposeAsync(fn);
CompletableFuture<String> f0 =
CompletableFuture.supplyAsync(
() -> "Hello World") //①
.thenApply(s -> s + " QQ") //②
.thenApply(String::toUpperCase);//③
System.out.println(f0.join());
// 输出结果
HELLO WORLD QQ
2.描述 AND 汇聚关系
CompletionStage<R> thenCombine(other, fn);
CompletionStage<R> thenCombineAsync(other, fn);
CompletionStage<Void> thenAcceptBoth(other, consumer);
CompletionStage<Void> thenAcceptBothAsync(other, consumer);
CompletionStage<Void> runAfterBoth(other, action);
CompletionStage<Void> runAfterBothAsync(other, action);
3.描述 OR 汇聚关系
CompletionStage applyToEither(other, fn);
CompletionStage applyToEitherAsync(other, fn);
CompletionStage acceptEither(other, consumer);
CompletionStage acceptEitherAsync(other, consumer);
CompletionStage runAfterEither(other, action);
CompletionStage runAfterEitherAsync(other, action);
4. 异常处理
fn、consumer、action 它们的核心方法都不允许抛出可检查异常,但是却无法限制它们抛出运行时异常,例如执行 7/0 就会出现除零错误这个运行时异常。非异步编程里面,我们可以使用 try{}catch{}来捕获并处理异常,那在异步编程里面,异常该如何处理呢?
CompletionStage 接口给我们提供的方案非常简单,比 try{}catch{}还要简单,下面是相关的方法,使用这些方法进行异常处理和串行操作是一样的,都支持链式编程方式。
CompletionStage exceptionally(fn);
CompletionStage<R> whenComplete(consumer);
CompletionStage<R> whenCompleteAsync(consumer);
CompletionStage<R> handle(fn);
CompletionStage<R> handleAsync(fn);
使用 exceptionally() 方法来处理异常
CompletableFuture<Integer>
f0 = CompletableFuture
.supplyAsync(()->7/0))
.thenApply(r->r*10)
.exceptionally(e->0);
System.out.println(f0.join());