@

最近在学习juc并发编程,于是决定汇总一下并发编程中常用方法,常见问题以及常见考题,今天是第一章—CompletableFuture

CompletableFuture介绍

CompletableFuture是jdk8版本开始出现的类。目的是为了应用于并发编程状态下遇到的各种场景。CompletableFuture实现了CompletionStage接口和Future接口,对Java7及以前Future接口做了大量的扩展,增加了许多常用方法,增加了异步会点、流式处理、多个Future组合处理的能力,使Java在处理多任务的协同工作时更加顺畅便利。

首先得学会看懂几个函数式接口的特性!!!(重点)

1.创建异步任务

  1. //runAsync方法不支持返回值
  2. public static CompletableFuture<Void> runAsync(Runnable runnable)
  3. public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)
  4. //supplyAsync可以支持返回值
  5. public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
  6. public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)

CompletableFuture创建异步任务主要分为两个方法runAsync和supplyAsync

其中

  1. runAsync方法不支持返回值
  2. supplyAsync可以支持返回值(常用)
  3. 没有指定Executor的方法会使用ForkJoinPool.commonPool() 作为它的线程池执行异步代码。如果指定线程池,则使用指定的线程池运行。

下面是四种创建方式

  1. public class CompletableFutureTest {
  2. public static void main(String[] args) throws Exception{
  3. ThreadPoolExecutor executor = new ThreadPoolExecutor(
  4. 5,5,5
  5. TimeUnit.MINUTES,
  6. new LinkedBlockingQueue<>(10));
  7. CompletableFuture future1=CompletableFuture.runAsync(()->{
  8. System.out.println(Thread.currentThread().getName()+"*********future1 coming in");
  9. });
  10. //这里获取到的值是null,无返回值
  11. System.out.println(future1.get());
  12. CompletableFuture<Void> future2 = CompletableFuture.runAsync(() -> {
  13. //ForkJoinPool.commonPool-worker-9
  14. System.out.println(Thread.currentThread().getName() + "\t" + "*********future2 coming in");
  15. }, executor);
  16. CompletableFuture<Integer> future3 =CompletableFuture.supplyAsync(()-> {
  17. //pool-1-thread-1
  18. System.out.println(Thread.currentThread().getName() + "\t" + "future3带有返回值");
  19. return "abc";
  20. });
  21. System.out.println(future3.get());
  22. CompletableFuture<Integer> future4 = CompletableFuture.supplyAsync(() -> {
  23. System.out.println(Thread.currentThread().getName() + "\t" + "future4带有返回值");
  24. return "abc";
  25. }, executor);
  26. System.out.println(future4.get());
  27. //关闭线程池
  28. executor.shutdown();
  29. }
  30. }

2.CompletableFuture API

①. 获得结果和触发计算(get、getNow、join、complete)

获得结果和触发计算(get、getNow、join、complete)

  1. public T get( ):不见不散(会抛出异常) 只要调用了get( )方法,不管是否计算完成都会导致阻塞
  2. public T get(long timeout, TimeUnit unit):过时不候
  3. public T getNow(T valuelfAbsent):没有计算完成的情况下,给我一个替代结果计算完,返回计算完成后的结果、没算完,返回设定的valuelfAbsent
  4. public T join( ):join方法和get( )方法作用一样,唯一区别是join方法编译时不需要手动抛异常

  1. public CompletableFuture thenApply:计算结果存在依赖关系,这两个线程串行化,由于存在依赖关系(当前步错,不走下一步),当前步骤有异常的话就叫停
  2. public CompletableFuture handle(BiFunction<? super T, Throwable, ? extends U> fn):有异常也可以往下一步走,根据带的异常参数可以进一步处理
  3. whenComplete:是执行当前任务的线程执行继续执行whenComplete的任务
  4. whenCompleteAsync:是执行把whenCompleteAsync这个任务继续提交给线程池来进行执行

②. 对计算结果进行处理(thenApply、handle)

  1. public CompletableFuture thenApply:计算结果存在依赖关系,这两个线程串行化由于存在依赖关系(当前步错,不走下一步),当前步骤有异常的话就叫停
  2. public CompletableFuture handle(BiFunction<? super T, Throwable, ? extends U> fn):有异常也可以往下一步走,根据带的异常参数可以进一步处理
  3. whenComplete:是执行当前任务的线程执行继续执行whenComplete的任务
  4. whenCompleteAsync:是执行把whenCompleteAsync这个任务继续提交给线程池来进行执行

  1. CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
  2. try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) {e.printStackTrace();}
  3. return 1;
  4. }).thenApply(s->{
  5. System.out.println("-----1");
  6. //如果加上int error=1/0; 由于存在依赖关系(当前步错,不走下一步),当前步骤有异常的话就叫停
  7. //int error=1/0;
  8. return s+1;
  9. }).thenApply(s->{
  10. System.out.println("-----2");
  11. return s+2;
  12. }).whenComplete((v,e)->{
  13. if(e==null){
  14. System.out.println("result-----"+v);
  15. }
  16. }).exceptionally(e->{
  17. e.printStackTrace();
  18. return null;
  19. });
  20. System.out.println(Thread.currentThread().getName()+"\t"+"over....");
  21. try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) {e.printStackTrace();}
  22. CompletableFuture.supplyAsync(() -> {
  23. return 1;
  24. }).handle((f,e) -> {
  25. System.out.println("-----1");
  26. return f + 2;
  27. }).handle((f,e) -> {
  28. System.out.println("-----2");
  29. int error=1/0;
  30. return f + 3;
  31. }).handle((f,e) -> {
  32. System.out.println("-----3");
  33. return f + 4;
  34. }).whenComplete((v, e) -> {
  35. if (e == null) {
  36. System.out.println("----result: " + v);
  37. }
  38. }).exceptionally(e -> {
  39. e.printStackTrace();
  40. return null;
  41. }).join());

③. 对计算结果进行消费(thenRun、thenAccept、thenApply)

  1. thenRun(Runnable runnable)

    任务A执行完执行B,并且B不需要A的结果
  2. CompletableFutur thenAccept(Consumer<? super T> action)

    任务A执行完成执行B,B需要A的结果,但是任务B无返回值
  3. public CompletableFuture thenApply(Function<? super T,? extends U> fn)



4. 线程串行化方法

带了Async的方法表示的是:会重新在线程池中启动一个线程来执行任务

  1. public <U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn)
  2. public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn)
  3. public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn, Executor executor)
  4. public CompletableFuture<Void> thenAccept(Consumer<? super T> action)
  5. public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action)
  6. public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action,Executor executor)
  7. public CompletableFuture<Void> thenRun(Runnable action)
  8. public CompletableFuture<Void> thenRunAsync(Runnable action)
  9. public CompletableFuture<Void> thenRunAsync(Runnable action,Executor executor)

④. 对计算速度进行选用(applyToEither、acceptEither、runAfterEither)

  1. public CompletableFuture applyToEither(CompletionStage<? extends T> other, Function<? super T, U> fn)

    这个方法表示的是,谁完成任务完成的快就返回谁的结果

  2. 两任务组合,一个完成

    applyToEither:两个任务有一个执行完成,获取它的返回值,处理任务并有新的返回值

    acceptEither:两个任务有一个执行完成,获取它的返回值,处理任务,没有新的返回值

    runAfterEither:两个任务有一个执行完成,不需要获取 future 的结果,处理任务,也没有返回值

⑤. 对计算结果进行合并(thenCombine、thenAcceptBoth、runAfterBoth)

  1. public <U,V> CompletableFuture thenCombine(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn)

    两个CompletionStage任务都完成后,最终把两个任务的结果一起交给thenCombine来处理先完成的先等着,等待其他分支任务

⑥. 多任务组合(allOf、anyOf)

  1. allOf:等待所有任务完成

    (public static CompletableFuture allOf(CompletableFuture<?>... cfs))
  2. anyOf:只要有一个任务完成

    (public static CompletableFuture anyOf(CompletableFuture<?>... cfs))

实战演练

案例说明:电商比价需求

同一款产品,同时搜索出同款产品在各大电商的售价;

同一款产品,同时搜索出本产品在某一个电商平台下,各个入驻门店的售价是多少

出来结果希望是同款产品的在不同地方的价格清单列表,返回一个List

in jd price is 88.05

in pdd price is 86.11

in taobao price is 90.43

(String类型)

代码示例

  1. public class test1 {
  2. public ExecutorService executorService = new ThreadPoolExecutor(40,100,100,TimeUnit.MINUTES,new ArrayBlockingQueue<>(10000));
  3. public static List<mall> list = Arrays.asList(
  4. new mall("jd"),
  5. new mall("tmall"),
  6. new mall("taobao"),
  7. new mall("pdd"),
  8. new mall("elm")
  9. );
  10. public static void main(String[] args) {
  11. showRes3(list,"mysql");
  12. }
  13. public static List<String> showRes(List<mall> list,String productName){
  14. long start = System.currentTimeMillis();
  15. List<String> collect = list.stream().map(mall -> String.format(productName + " in %s price is %.2f", mall.getName(), mall.getPrice(productName))).collect(Collectors.toList());
  16. collect.forEach(System.out::println);
  17. long end = System.currentTimeMillis();
  18. System.out.println("耗时:"+(end-start)+"秒");
  19. return collect;
  20. }
  21. public static List<String> showRes2(List<mall> list,String productName){
  22. long start = System.currentTimeMillis();
  23. List<String> collect = list.parallelStream().map(mall -> CompletableFuture.supplyAsync(() ->
  24. String.format(productName + " in %s price is %.2f", mall.getName(), mall.getPrice(productName))
  25. ).join()).collect(Collectors.toList());
  26. long end = System.currentTimeMillis();
  27. collect.forEach(System.out::println);
  28. System.out.println("耗时:"+(end-start)+"秒");
  29. return collect;
  30. }
  31. public static List<String> showRes3(List<mall> list,String productName){
  32. long start = System.currentTimeMillis();
  33. List<String> collect = list.stream().map(mall -> CompletableFuture.supplyAsync(() -> String.format(productName + " in %s price is %.2f", mall.getName(), mall.getPrice(productName))
  34. )).collect(Collectors.toList()).stream().map(s -> s.join()).collect(Collectors.toList());
  35. long end = System.currentTimeMillis();
  36. collect.forEach(System.out::println);
  37. System.out.println("耗时:"+(end-start)+"毫秒");
  38. return collect;
  39. }
  40. }
  41. class mall{
  42. private String name;
  43. public String getName() {
  44. return name;
  45. }
  46. public mall(String name) {
  47. this.name = name;
  48. }
  49. public double getPrice(String productName){
  50. try {
  51. TimeUnit.SECONDS.sleep(1);
  52. } catch (InterruptedException e) {
  53. throw new RuntimeException(e);
  54. }
  55. return ThreadLocalRandom.current().nextDouble()*2+productName.charAt(0);
  56. }
  57. }

通过上面代码示例结果得知

通过异步线程方法耗时需要1000毫秒左右

而同步线程方法耗时需要5000毫秒

而且当商城店铺越来越多的时候,异步线程耗时不会增加,同步线程耗时会不断增加,异步的优势就显现出来了。

tips

在使用stream流的时候,如果使用的是集合的stream()方法,再使用异步线程时比如使用join()方法会出现串行,和上面方法三一样得使用两次stream流才能实现异步执行,相当于每一步都得使用stream流。

这个时候可以使用parallelStream并行流就可以解决这个问题

但是使用parallelStream会出现一些问题,所以并行流要谨慎使用!!!

并行流的陷阱

  1. 线程安全

    由于并行流使用多线程,则一切线程安全问题都应该是需要考虑的问题,如:资源竞争、死锁、事务、可见性等等。
  2. 线程消费

    在虚拟机启动时,我们指定了worker线程的数量,整个程序的生命周期都将使用这些工作线程;这必然存在任务生产和消费的问题,如果某个生产者生产了许多重量级的任务(耗时很长),那么其他任务毫无疑问将会没有工作线程可用;更可怕的事情是这些工作线程正在进行IO阻塞。

JUC并发编程(1)—CompletableFuture详解的更多相关文章

  1. 多线程JUC并发篇常见面试详解

    @ 目录 1.JUC 简介 2.线程和进程 3.并非与并行 4.线程的状态 5.wait/sleep的区别 6.Lock 锁(重点) 1.Lock锁 2.公平非公平: 3.ReentrantLock ...

  2. Java 并发编程 | 线程池详解

    原文: https://chenmingyu.top/concurrent-threadpool/ 线程池 线程池用来处理异步任务或者并发执行的任务 优点: 重复利用已创建的线程,减少创建和销毁线程造 ...

  3. 并发编程——IO模型详解

    ​ 我是一个Python技术小白,对于我而言,多任务处理一般就借助于多进程以及多线程的方式,在多任务处理中如果涉及到IO操作,则会接触到同步.异步.阻塞.非阻塞等相关概念,当然也是并发编程的基础. ​ ...

  4. Android并发编程之白话文详解Future,FutureTask和Callable

    从最简单的说起Thread和Runnable 说到并发编程,就一定是多个线程并发执行任务.那么并发编程的基础是什么呢?没错那就是Thread了.一个Thread可以执行一个Runnable类型的对象. ...

  5. 从缓存入门到并发编程三要素详解 Java中 volatile 、final 等关键字解析案例

    引入高速缓存概念 在计算机在执行程序时,以指令为单位来执行,每条指令都是在CPU中执行的,而执行指令过程中,势必涉及到数据的读取和写入. 由于程序运行过程中的临时数据是存放在主存(物理内存)当中的,这 ...

  6. 并发编程 || Java线程详解

    通用线程模型 在很多研发当中,实际应用是基于一个理论再进行优化的.所以,在了解JVM规范中的Java线程的生命周期之前,我们可以先了解通用的线程生命周期,这有助于我们后续对JVM线程生命周期的理解. ...

  7. java 并发编程lock使用详解

    浅谈Synchronized: synchronized是Java的一个关键字,也就是Java语言内置的特性,如果一个代码块被synchronized修饰了,当一个线程获取了对应的锁,执行代码块时,其 ...

  8. 跟着阿里p7一起学java高并发 - 第19天:JUC中的Executor框架详解1,全面掌握java并发核心技术

    这是java高并发系列第19篇文章. 本文主要内容 介绍Executor框架相关内容 介绍Executor 介绍ExecutorService 介绍线程池ThreadPoolExecutor及案例 介 ...

  9. java高并发系列 - 第20天:JUC中的Executor框架详解2之ExecutorCompletionService

    这是java高并发系列第20篇文章. 本文内容 ExecutorCompletionService出现的背景 介绍CompletionService接口及常用的方法 介绍ExecutorComplet ...

  10. Java网络编程和NIO详解开篇:Java网络编程基础

    Java网络编程和NIO详解开篇:Java网络编程基础 计算机网络编程基础 转自:https://mp.weixin.qq.com/s/XXMz5uAFSsPdg38bth2jAA 我们是幸运的,因为 ...

随机推荐

  1. pta第三阶段题目集

    (1)前言 pta第三阶段作业中,主要包含了如下的主要内容: 1.全程贯穿了课程设计的程序,每一次都是上一次的迭代和修改,难度较大,中间涉及到先是类与类之间的多态和继承关系,后面的修改中,转变为了组合 ...

  2. HCL实验:4.同一vlan位于不同交换机上的通信

    拓扑图 SW1配置 SW2配置 SW3配置 VLAN 1 PC1 PING PC5 VLAN 2 PC 2 PING PC 6 VLAN 3 PC4 PING PC8 怀疑是模拟器的问题---- 不知 ...

  3. ChatGPT「代码解释器」正式开放,图片转视频仅需30秒!十大令人震惊的魔法揭秘

    经过超过三个月的等待,ChatGPT「代码解释器」终于全面开放.这是一大波神奇魔法的高潮. OpenAI的科学家Karpathy对这个强大的代码解释器测试版赞不绝口.他把它比作你的个人数据分析师,可以 ...

  4. 打开页面就能玩?ZEGO 发布行业首套 Web 端在线 KTV 解决方案

    近些年,在线K歌行业逐渐开始规模化发展,活跃用户不断破新高,据 ZEGO 即构科技研究院数据显示,2021 年中国在线 K 歌用户规模逾 5.2亿,网民渗透率达到 50%. 凭借着极大的便利性和娱乐性 ...

  5. 踏入数字天地之中 | Metaworld SDK 2.0进化纵览

    ​ ZEGO从未停止对技术边界的探索,我们力图让用户能够更高效.便捷地使用技术去创造价值. 去年8月,ZEGO打造的元宇宙智能互动引擎首次与大家见面,Metaworld SDK作为其中的核心能力组件, ...

  6. Hexo博客Next主题阅读次数热度不能读取的问题,报错Counter not initialized! More info at console err msg.

    加入valine在线评论 设置效果: 设置方法: 首先要先去LeanCloud注册一个帐号.然后再创建一个应用. 拿到appid和appkey之后,打开themes/next/_config.yml主 ...

  7. Java开发大型互联网-架构师必须掌握的分布式技术

    Java开发大型互联网-架构师必须掌握的分布式技术 摘要:在当今互联网行业,随着用户量和业务的不断增长,大型互联网系统的设计和开发已经成为了一项头等重要的任务.作为架构师,要能够应对这样的挑战,就必须 ...

  8. Spring-Bean(三)

    Bean生命周期配置 init-method:指定类中的初始化方法名称 destory-method:指定类中销毁方法名称 Bean标签配置 <bean id="UserDao&quo ...

  9. ESP32连接云服务器【WebSocket】

    ESP32连接云服务器[ESP32+宝塔面板] 相关文章 ESP32连接MQ Sensor实现气味反应 https://blog.csdn.net/ws15168689087/article/deta ...

  10. QMainWindow类中比较重要的方法

    方法和描述 addToolBar():添加工具栏 centralWidget():返回窗口中心的一个空间,未设置时返回NULL menuBar(): 返回主窗口的菜单栏 setCentralWidge ...