一. 线程池
1. 初始化线程的4种方式
- 方式1 : 继承Tread
- 方式2 : 实现Runnable接口
- 方式3 : 实现Callable接口+FutureTask(可以拿到返回结果,可以处理异常)
- 方式4 : 线程池
-
方式1和方式2 : 主进程无法获取线程的运算结果
-
方式3 : 主进程可以获取线程的运算结果,但是不利于控制服务器中的线程资源,可能会导致服务器资源耗尽
-
方式4 : 通过如下两种方式初始化线程池:
-
Executors.newFiexedThreadPool(3); //或者 new ThreadPoolExecutor(corePoolSize,maximumPoolSize,keepAliveTime,TimeUnit,unit,workQueue,threadFactory,handler);
-
-
通过线程池性能稳定,也可以获取执行结果,并捕获异常,但是,在业务复杂情况下,一个异步调用可能会依赖另一个异步调用的执行结果
2. 线程池的七大参数
- corePoolSize : 线程池常驻核心线程数,创建线程池后,当有请求任务来之后,就会安排池中线程去执行请求任务,近似理解为今日当值线程.当线程池中的线程数目达到了corePoolSize后,就会把任务放到缓存队列中;
- maxmumPoolSize : 线程池能够容纳同时执行的最大线程数,此值必须大于等于1
- keepAliveTime : 多余空闲线程的存活时间.当前线程池的数量超过corePoolSize时,当空闲时间达到keepAliveTime值时,多余空闲线程会被销毁直到只剩下corePoolSize个线程为止。
- unit : keepAliveTime的时间单位
- workQueue : 任务队列,被提交但未被执行的任务
- threadFactory : 表示生成线程池中线程的线程工厂,用于创建线程,一般用默认的即可
- handler : 拒绝策略,表示当队列满了并且工作线程大于等于线程池的最大线程数(maxmumPoolSize)
3. 常见的四种线程池
- newFixedThreadPool(定长线程池) : 一个有指定的线程数的线程池,有核心的线程,里面有固定的线程数量,响应的速度快.正规的并发线程,多用于服务器.固定的线程数由系统资源设置,核心线程是没有超时机制的,队列大小没有限制,除非线程池关闭了核心线程才会被回收.
- newCachedThreadPool(可缓冲线程池) : 只有非核心线程,最大线程数很大,每新来一个任务,当没有空余线程的时候就会重新创建一个线程,这边有一个超时机制,当空闲的线程超过60s内没有用到的话,就会被回收,它可以一定程序减少频繁创建/销毁线程,减少系统开销,适用于执行时间短并且数量多的任务场景.
- ScheduledThreadPool(周期线程池) : 创建一个定长线程池,支持定时及周期性任务执行,通过过schedule方法可以设置任务的周期执行
- newSingleThreadExecutor(单任务线程池) : 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行,每次任务到来后都会进入阻塞队列,然后按指定顺序执行.
4. 开发中为什么使用线程池
- 降低资源的消耗 : 通过重复利用已经建好的线程降低线程的创建和销毁带来的损耗
- 提供响应速度 : 因为线程池中的线程数没有超过线程池的最大上限时,有的线程处于等待分配任务的状态,当任务来时无需创建新的线程就能执行
- 提供线程的可管理性 : 线程池会根据当前系统特点对池内的线程进行优化处理,减少创建和销毁线程带来的系统开销,无限的创建和销毁线程不仅消耗系统资源,还降低系统的稳定性,使用线程池进行统一分配
二. CompletableFuture异步编排
-
应用场景: 查询商品详情页的逻辑比较复杂,有些数据还需要远程调用,必然花费更多时间.
- 获取sku的基本信息 0.5s
- 获取sku的图片信息 0.5s
- 获取sku的促销信息 1s
- 获取spu的所有销售属性 1s
- 获取规格参数组及组下的规格参数 1.5s
- spu详情 1s
-
假如商品详情的每个查询,需要如上标注的时间才能完成,那么,用户需要5.5s之后才能看到商品详情页的内容,很显然是不能接受的,如果有多个线程同时完成这6步操作,也许只需要1.5s即可完成响应
1. 创建异步对象
-
注: 方法不以Async结尾,意味着Action使用相同的线程执行,而Async会重新把Action提交给线程池,可能会使用其他线程执行(如果使用相同的线程池,也就可能会被同一个线程选择执行)
-
通过构造方法创建CompletableFuture,然后在线程中将需要得到的结果通过CompletableFuture的complete方法传入,从而监控线程任务的执行状态.
CompletableFuture<Integer> future = new CompletableFuture<>();//当对象被创建时,任务就开始了 new Thread(()->{ System.out.println("当前线程: " + Thread.currentThread().getName()); int i = 4 / 2; future.complete(i);//此方法,以为任务完成,返回结果 System.out.println("结果: "+i); }).start(); Integer result = future.get();//阻塞方法,直到任务完成. System.out.println("返回结果: " + result);
-
如果需要执行一个不需要返回的任务时,就可以使用runAsync,运行异步任务,没有返回
public static ExecutorService pool = Executors.newFixedThreadPool(10); CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {//他持有一个Runnblbe对象,并返回CompletableFuture<Void> ,所以可以链式编程 System.out.println("当前线程: " + Thread.currentThread().getName()); int i = 4 / 2; System.out.println("结果: "+i); },pool);
-
如果需要返回的话,也可以使用supplyAsync
public static ExecutorService pool = Executors.newFixedThreadPool(10); CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> { System.out.println("当前线程: " + Thread.currentThread().getName()); int i = 4 / 0; System.out.println("结果: " + i); return i; }, pool); Integer result = future.get();//阻塞方法,直到任务完成. System.out.println("返回结果: " + result);
2.任务完成时,回调方法
-
whenComplete可以处理正常和异常的计算结果,exceptionally处理异常情况
-
whenComplete和whenCompleteAsync的区别:前者是执行当前任务的线程继续执行whenComplete的任务,后者是把whenCompleleAsync这个任务继续提交给线程池进行执行
public static ExecutorService pool = Executors.newFixedThreadPool(10); CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> { System.out.println("当前线程: " + Thread.currentThread().getName()); int i = 4 / 0; System.out.println("结果: " + i); return i; }, pool).whenComplete((res, throwable) -> { //虽然能得到异常信息,但是无法修改返回数据 System.out.println("异步任务完成....结果: " + res + " 异常: " + throwable); }).exceptionally(throwable -> { //可以感知到异常,同时返回默认值 return 10; }); Integer result = future.get();//阻塞方法,直到任务完成. System.out.println("返回结果: " + result);
3.handle方法
-
和whenComplete作用类似,也是回调方法,但是handle方法是可以修改结果的,如果是线程串行化,这个结果可以影响后面的结果
public static ExecutorService pool = Executors.newFixedThreadPool(10); CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> { System.out.println("当前线程: " + Thread.currentThread().getName()); int i = 4 / 2; System.out.println("结果: " + i); return i; }, pool).handle((res, throwable) -> { System.out.println("异步任务完成....结果: " + res + " 异常: " + throwable); if (throwable == null) return res; return 10; }); Integer result = future.get();//阻塞方法,直到任务完成. System.out.println("返回结果: " + result);
4.线程串行化方法
- 串行化: 这个任务的开始或者执行,需要依赖另一个任务的结果,get方法是一个阻塞方法,即这个任务的执行,需要等待另一个任务执行完成
- thenRun方法: 只要上面的方法执行完成,就开始执行zhenRun,不接收上一步结果,也不返回结果给下一步
- thenApply方法: 上一个方法执行完成,就开始执行,接收上一步结果,并返回结果给下一步
- thenAccept方法:…接收结果,不返回结果
5. 聚合
- ann聚合: 两个任务都要完成,才执行下一个任务
- thenCombine 有参数 有返回
- thenAcceptBoth 有参数 无返回
- runAfterBoth 无参数 无返回
- or聚合: 两个任务只要有一个完成,就执行下一个任务
- applyToEither 有参数 有返回
- acceptEither 有参数 无返回
- runAfterEither 无参数 无返回
- 如果聚合多个,可以用CompletableFuture的静态方法allOf和anyOf