CompletableFuture异步编排
为什么使用多线程
问题:查询商品详情页的数据,都需要通过远程调用获取到,这样操作的话,必然需要花费更多的时间。
假如商品详情页的每个查询,需要如下标注的时间才能完成
- 获取sku的信息 1.5s
- 获取sku对应的优惠券信息 0.5s
- 更新sku商品的热度 1s
那么,用户需要3s后才能看到商品详情页的内容。很显然是不能接受的。
如果有多个线程同时完成这3步操作,也许只需要1.5s即可完成响应,极大缩短了响应时间。
前置知识
进程和线程
进程:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。
线程:通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义。线程可以利用进程所拥有的资源,在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位,由于线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效的提高系统多个程序间并发执行的程度。
并发、串行和并行
(1)并发: 同一时刻多个线程在访问同一个资源,多个线程对一个点
例子:春运抢票、微信抢红包、电商秒杀…
(2)串行-同步串行: 代表多任务按先后顺序执行,并且都是同一个线程来执行。
(3)串行-异步串行: 代表多任务按先后顺序执行,并由不同的线程来执行。
(4)并行: 多项任务一起执行,之后再汇总
例子:泡方便面,电水壶烧水,一边撕调料倒入桶中。
(5)任务合并: 一个任务的执行依赖于前面多个任务执行的返回值,并且这些任务可以由同一个线程执行,也可以由不同的线程执行。
创建线程的方式
第一种 继承Thread类
第二种 实现Runnable接口
第三种 使用Callable接口
第四种 使用线程池
线程池
(1)线程池的优势: 线程复用;控制最大并发数;管理线程。
(2)创建线程池
(3)线程池的7个重要参数
- corePoolSize:线程池中的常驻核心线程数
- maximumPoolSize:线程池中能够容纳同时 执行的最大线程数,此值必须大于等于1
- keepAliveTime:多余的空闲线程的存活时间 当前池中线程数量超过corePoolSize时,当空闲时间达到keepAliveTime时,多余线程会被销毁直到 只剩下corePoolSize个线程为止
- unit:keepAliveTime的单位
- workQueue:任务队列,被提交但尚未被执行的任务
- threadFactory:表示生成线程池中工作线程的线程工厂, 用于创建线程,一般默认的即可
- handler:拒绝策略,表示当队列满了,并且工作线程大于 等于线程池的最大线程数(maximumPoolSize)时,如何来拒绝 请求执行的runnable的策略.
CompletableFuture概述
Future是Java 5添加的类,用来描述一个异步计算的结果。你可以使用isDone方法检查计算是否完成,或者使用get阻塞住调用线程,直到计算完成返回结果,你也可以使用cancel方法停止任务的执行。
在Java 8中, CompletableFuture提供了非常强大的Future的扩展功能,可以帮助我们简化异步编程的复杂性,提供了函数式编程的能力,可以通过回调的方式处理计算结果,并且提供了转换和组合CompletableFuture的方法。
CompletableFuture和FutureTask同属于Future接口的实现类,都可以获取线程的执行结果。
创建异步对象
CompletableFuture 提供了四个静态方法来创建一个异步操作。
没有指定Executor的方法会使用ForkJoinPool.commonPool() 作为它的线程池执行异步代码。如果指定线程池,则使用指定的线程池运行。
- runAsync方法不支持返回值。
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Demo1 {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(3);
System.out.println("main begin....");
CompletableFuture<Void> completableFuture =
CompletableFuture.runAsync(()->{
System.out.println("当前线程:"+Thread.currentThread().getId());
int result = 1024;
System.out.println("result:"+result);
},executorService);
System.out.println("main over....");
}
}
main begin....
main over....
当前线程:12
result:1024
- supplyAsync可以支持返回值。
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Demo2 {
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newFixedThreadPool(3);
System.out.println("main begin....");
CompletableFuture<Integer> completableFuture =
CompletableFuture.supplyAsync(()->{
System.out.println("当前线程:"+Thread.currentThread().getId());
int result = 1024;
System.out.println("result:"+result);
return result;
},executorService);
//获取返回结果
Integer value = completableFuture.get();
System.out.println("main over...."+value);
}
}
main begin....
当前线程:12
result:1024
main over....1024
计算完成时回调方法
当CompletableFuture的计算结果完成,或者抛出异常的时候,可以执行特定的Action。主要是下面的方法:
whenComplete可以处理正常或异常的计算结果,exceptionally处理异常情况。BiConsumer<? super T,? super Throwable>可以定义处理业务
whenComplete 和 whenCompleteAsync 的区别:
whenComplete:是执行当前任务的线程执行继续执行 whenComplete 的任务。
whenCompleteAsync:是执行把 whenCompleteAsync 这个任务继续提交给线程池来进行执行。
方法不以Async结尾,意味着Action使用相同的线程执行,而Async可能会使用其他线程执行(如果是使用相同的线程池,也可能会被同一个线程选中执行)
public class Demo3 {
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newFixedThreadPool(3);
System.out.println("main begin....");
CompletableFuture<Integer> completableFuture =
CompletableFuture.supplyAsync(()->{
System.out.println("当前线程:"+Thread.currentThread().getId());
int result = 10/0;
System.out.println("result:"+result);
return result;
},executorService)
.whenComplete((rs,exception)->{
System.out.println("结果:"+rs);
System.out.println(exception);
});
System.out.println("main over....");
}
}
main begin....
当前线程:12
结果:null
java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
main over....
线程串行化方法
thenApply 方法:当一个线程依赖另一个线程时,获取上一个任务返回的结果,并返回当前任务的返回值。
thenAccept方法:消费处理结果。接收任务的处理结果,并消费处理,无返回结果。
thenRun方法:只要上面的任务执行完成,就开始执行thenRun,只是处理完任务后,执行 thenRun的后续操作
带有Async默认是异步执行的。这里所谓的异步指的是不在当前线程内执行。
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Demo4 {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(3);
// 线程1执行返回的结果:100
CompletableFuture<Integer> futureA =
CompletableFuture.supplyAsync(() -> {
int res = 100;
System.out.println("线程一:"+res);
return res;
});
// 线程2 获取到线程1执行的结果
CompletableFuture<Integer> futureB = futureA.thenApplyAsync((res)->{
System.out.println("线程二--"+res);
return ++res;
},executorService);
//线程3: 无法获取futureA返回结果
CompletableFuture<Void> futureC = futureA.thenRunAsync(() -> {
System.out.println("线程三....");
}, executorService);
}
}
线程一:100
线程二--100
线程三....
多任务组合
public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs);
public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs);
- allOf:等待所有任务完成。
- anyOf:只要有一个任务完成。
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Demo5 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(4);
// 线程1
CompletableFuture<Integer> futureA =
CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName()+"--begin..");
int res = 100;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("一:"+res);
System.out.println(Thread.currentThread().getName()+"--over..");
return res;
},executorService);
// 线程2
CompletableFuture<Integer> futureB =
CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName()+"--begin..");
int res = 30;
System.out.println("二:"+res);
System.out.println(Thread.currentThread().getName()+"--over..");
return res;
},executorService);
CompletableFuture<Void> all = CompletableFuture.allOf(futureA,futureB);
all.get();
System.out.println("over....");
}
}
pool-1-thread-1--begin..
pool-1-thread-2--begin..
二:30
pool-1-thread-2--over..
一:100
pool-1-thread-1--over..
over....