一、简介

平常在页面中我们会使用异步调用$.ajax()函数,如果是多个的话他会并行执行相互不影响,实际上Completable我理解也是和它类似,是java 8里面新出的异步实现类,CompletableFuture类实现了Future接口,CompletableFuture与Stream的设计都遵循了类似的设计模式:使用Lambda表达式以及流水线的思想,从这个角度可以说CompletableFuture与Future的关系类似于Stream与Collection的关系。

二、代码

直接上代码,运行之后可以看出CompletableFuture是调用的时候就开始执行,当后续代码调到get的取值方法时,如果内部已经返回结果则直接拿到,如果还没有返回将阻塞线程等待结果,可以设置超时时间避免长时间等待。

以下是模拟并行调用多个方法的场景,比如查询页可能会有多个条件选择,这些条件需要后台数据相互之间有没有联系的场景,就不需要串行执行,异步执行可以节省大量时间

  1. import org.joda.time.LocalDateTime;
  2. import org.junit.Test;
  3. import java.util.Arrays;
  4. import java.util.List;
  5. import java.util.concurrent.CompletableFuture;
  6. import java.util.concurrent.ExecutionException;
  7. import java.util.concurrent.TimeUnit;
  8. import java.util.stream.Collectors;
  9.  
  10. public class AppTest {
  11.  
  12. public void printlnConsole(String msg) {
  13. String time = new LocalDateTime().toString("yyyy-MM-dd HH:mm:ss");
  14. System.out.println(String.format("[%s]%s", time, msg));
  15. }
  16.  
  17. /**
  18. * 多任务单次异步执行
  19. */
  20. @Test
  21. public void testManyFunAsync() {
  22. long start = System.nanoTime();//程序开始时间
  23. try {
  24. int id = 1;//模拟一个参数,如学校Id
  25. printlnConsole("调用异步任务...");
  26. //使用异步方式调用方法【调用时就会开始执行方法】
  27. CompletableFuture futureClassCount = CompletableFuture.supplyAsync(() -> getClassCount(id));
  28. CompletableFuture futureStudentCount = CompletableFuture.supplyAsync(() -> getStudentCount(id));
  29.  
  30. //do something 做了一些其他的事情超过了异步任务执行的时间
  31. printlnConsole("做一些其他的事情...");
  32. Thread.sleep(3000);
  33. printlnConsole("其他事情完成");
  34.  
  35. //下面获取异步任务的结果,就会立即拿到返回值
  36. printlnConsole("获取异步任务结果...");
  37. Object classCount = futureClassCount.get();
  38. //Object classCount = futureClassCount.get(2, TimeUnit.SECONDS);//可以设置超时时间,超过这个时间时将不再等待,返回异常
  39. Object studentCount = futureStudentCount.get();
  40. //Object studentCount = futureStudentCount.get(2, TimeUnit.SECONDS);
  41. printlnConsole("异步任务结果获取完成");
  42.  
  43. printlnConsole("ClassCount:" + classCount);
  44. printlnConsole("StudentCount:" + studentCount);
  45.  
  46. } catch (Exception e) {
  47. e.printStackTrace();
  48. }
  49. long end = System.nanoTime();//程序结束时间
  50. long time = (end - start) / 1000000;//总耗时
  51. System.out.println("运行时间:" + time);
  52. }
  53.  
  54. public int getClassCount(int id) {
  55. try {
  56. Thread.sleep(2000);
  57. printlnConsole("getClassCount(" + id + ")执行完毕");
  58. } catch (InterruptedException e) {
  59. e.printStackTrace();
  60. }
  61. return 20;
  62. }
  63.  
  64. public int getStudentCount(int id) {
  65. try {
  66. Thread.sleep(1000);
  67. printlnConsole("getStudentCount(" + id + ")执行完毕");
  68. } catch (InterruptedException e) {
  69. e.printStackTrace();
  70. }
  71. return 100;
  72. }
  73. }

anyOf()为任意一个子任务线程执行完毕后返回
allOf()为等待所有子任务线程全部执行完毕后返回
getNow()表示我需要立即拿到结果,如果当前的线程并未执行完成,则使用我传入的值进行任务调用,参数为无法获取结果时使用我传入的值
get()获取子线程运算的结果,会抛出检查到的异常
join()获取子线程运算的结果,不会抛出异常

  1. package com.ysl;
  2.  
  3. import org.joda.time.LocalDateTime;
  4. import org.junit.Test;
  5.  
  6. import java.util.Arrays;
  7. import java.util.List;
  8. import java.util.concurrent.*;
  9. import java.util.stream.Collectors;
  10.  
  11. public class AppTest {
  12.  
  13. public void printlnConsole(String msg) {
  14. String time = new LocalDateTime().toString("yyyy-MM-dd HH:mm:ss");
  15. System.out.println(String.format("[%s]%s", time, msg));
  16. }
  17.  
  18. /**
  19. * 并行执行等待全部结果或等待任意结果
  20. */
  21. @Test
  22. public void testAllOfAnyOf() {
  23. long start = System.nanoTime();
  24. try {
  25. printlnConsole("调用异步任务...");
  26. List<Integer> ids = Arrays.asList(1, 3, 5);//准备的请求参数
  27. //创建异步方法数组
  28. CompletableFuture[] futures = ids.stream().map(id -> CompletableFuture.supplyAsync(() -> getClassName(id))).toArray(size -> new CompletableFuture[size]);
  29. //指定该异步方法数组的子任务线程等待类型
  30. CompletableFuture.anyOf(futures).join();//anyOf()为任意一个子任务线程执行完毕后返回
  31. //CompletableFuture.allOf(futures).join();//allOf()为等待所有子任务线程全部执行完毕后返回
  32.  
  33. printlnConsole("做一些其他的事情...");
  34. Thread.sleep(2000);
  35. printlnConsole("其他事情完成");
  36.  
  37. printlnConsole("获取异步任务结果:");
  38. for (CompletableFuture f : futures) {
  39. //Object obj = f.getNow(1);//getNow()表示我需要立即拿到结果,如果当前的线程并未执行完成,则使用我传入的值进行任务调用,参数为无法获取结果时使用我传入的值
  40. Object obj = f.get();//get()获取子线程运算的结果,会抛出检查到的异常
  41. //Object obj = f.join();//join()获取子线程运算的结果,不会抛出异常
  42. printlnConsole(String.valueOf(obj));
  43. }
  44. } catch (Exception e) {
  45. e.printStackTrace();
  46. }
  47.  
  48. long end = System.nanoTime();
  49. long time = (end - start) / 1000000;
  50. System.out.println("运行时间:" + time);
  51. }
  52.  
  53. public String getClassName(int id) {
  54. try {
  55. Thread.sleep(id * 1000);
  56. printlnConsole("getClassName(" + id + ")执行完毕");
  57. } catch (InterruptedException e) {
  58. e.printStackTrace();
  59. }
  60. return "taiyonghai-" + id;
  61. }
  62. }

下面是并行流的演示parallelStream也是java 8新特性

ids.stream()转化为流.map()映射每个元素对应的结果.collect(Collectors.toList)把结果归纳为List;还有.filter()过滤元素.sorted()对元素进行排序.limit()获取指定数量元素;也可以toArray(size -> new Class[size])转化为数组

以下是模拟根据Id查询学生名称的场景,接收到的是一个集合又都是调用同一个方法获取,就可以使用并行流同时异步请求等待返回结果

  1. import org.joda.time.LocalDateTime;
  2. import org.junit.Test;
  3. import java.util.Arrays;
  4. import java.util.List;
  5. import java.util.concurrent.CompletableFuture;
  6. import java.util.concurrent.ExecutionException;
  7. import java.util.concurrent.TimeUnit;
  8. import java.util.stream.Collectors;
  9.  
  10. public class AppTest {
  11.  
  12. public void printlnConsole(String msg) {
  13. String time = new LocalDateTime().toString("yyyy-MM-dd HH:mm:ss");
  14. System.out.println(String.format("[%s]%s", time, msg));
  15. }
  16.  
  17. /**
  18. * 单任务多次并行流执行
  19. */
  20. @Test
  21. public void testParallelStream() {
  22. long start = System.nanoTime();
  23. try {
  24. printlnConsole("调用异步任务...");
  25. List<Integer> ids = Arrays.asList(1, 2, 3, 4, 5);//准备的请求参数
  26. //串行执行会等待每一个方法执行完毕后在继续执行下一个
  27. //List<String> names = ids.stream().map(id -> getStudentName(id)).collect(Collectors.toList());
  28. //并行执行会同时调用多个方法待全部执行完毕后一起返回(parallelStream是非线程安全的,配合collect达到线程安全,后续验证一下)
  29. List<String> names = ids.parallelStream().map(id -> getStudentName(id)).collect(Collectors.toList());
  30. //无论stream()或者parallelStream()调用时均会阻断线程执行
  31. printlnConsole("做一些其他的事情...");
  32. Thread.sleep(3000);
  33. printlnConsole("其他事情完成");
  34.  
  35. printlnConsole("获取异步任务结果:");
  36. names.forEach(item -> printlnConsole(item));
  37. } catch (Exception e) {
  38. e.printStackTrace();
  39. }
  40. long end = System.nanoTime();
  41. long time = (end - start) / 1000000;
  42. System.out.println("运行时间:" + time);
  43. }
  44.  
  45. public String getStudentName(int id) {
  46. try {
  47. Thread.sleep(2000);
  48. printlnConsole("getStudentName(" + id + ")执行完毕");
  49. } catch (InterruptedException e) {
  50. e.printStackTrace();
  51. }
  52. return "taiyonghai-" + id;
  53. }
  54. }

上面能看到并行流虽然是并行执行但等待结果是阻塞线程的,所以可以利用异步CompletableFuture配合串行流来实现

以下是采用串行流配合异步实现的并发处理

  1. import org.joda.time.LocalDateTime;
  2. import org.junit.Test;
  3. import java.util.Arrays;
  4. import java.util.List;
  5. import java.util.concurrent.CompletableFuture;
  6. import java.util.concurrent.ExecutionException;
  7. import java.util.concurrent.TimeUnit;
  8. import java.util.stream.Collectors;
  9.  
  10. public class AppTest {
  11.  
  12. public void printlnConsole(String msg) {
  13. String time = new LocalDateTime().toString("yyyy-MM-dd HH:mm:ss");
  14. System.out.println(String.format("[%s]%s", time, msg));
  15. }
  16.  
  17. /**
  18. * 单任务多次异步执行
  19. */
  20. @Test
  21. public void testOneFunAsync() {
  22. long start = System.nanoTime();
  23. try {
  24. printlnConsole("调用异步任务...");
  25. List<Integer> ids = Arrays.asList(1, 2, 3, 4, 5);//准备的请求参数
  26. //ids.stream()转化为流.map()映射每个元素对应的结果.collect()把结果归纳为List;还有.filter()过滤元素.sorted()对元素进行排序.limit()获取指定数量元素;
  27. List<CompletableFuture<String>> futures = ids.stream().map(id -> CompletableFuture.supplyAsync(() -> getStudentName(id))).collect(Collectors.toList());
  28.  
  29. //不用并行流parallelStream()调用时就不会阻断线程执行
  30. printlnConsole("做一些其他的事情...");
  31. Thread.sleep(3000);
  32. printlnConsole("其他事情完成");
  33.  
  34. printlnConsole("获取异步任务结果:");
  35. futures.forEach(f -> {
  36. try {
  37. Object obj = f.get();
  38. printlnConsole(String.valueOf(obj));
  39. } catch (InterruptedException e) {
  40. e.printStackTrace();
  41. } catch (ExecutionException e) {
  42. e.printStackTrace();
  43. }
  44. });
  45. }catch (Exception e){
  46. e.printStackTrace();
  47. }
  48.  
  49. long end = System.nanoTime();
  50. long time = (end - start) / 1000000;
  51. System.out.println("运行时间:" + time);
  52. }
  53.  
  54. public String getStudentName(int id) {
  55. try {
  56. Thread.sleep(2000);
  57. printlnConsole("getStudentName(" + id + ")执行完毕");
  58. } catch (InterruptedException e) {
  59. e.printStackTrace();
  60. }
  61. return "taiyonghai-" + id;
  62. }
  63. }

当我的并行任务数量超过了我机器的核心数就会产生等待,我电脑是8核使用并行流执行数量就可以开8个子线程,当多余这个数量时剩下的就需要等待前面线程执行完再执行

当需要并行执行的任务数量大于核心数的时候,产生的等待是我们不想看到的,这时CompletableFuture就更加适用,它可以手动这只线程池大小,避免并行任务过多时的等待

我们将代码做些修正

以下是源码,这样就可以提高对多任务并行处理的支持了

  1. import org.joda.time.LocalDateTime;
  2. import org.junit.Test;
  3.  
  4. import java.util.Arrays;
  5. import java.util.List;
  6. import java.util.concurrent.*;
  7. import java.util.stream.Collectors;
  8.  
  9. public class AppTest {
  10.  
  11. public void printlnConsole(String msg) {
  12. String time = new LocalDateTime().toString("yyyy-MM-dd HH:mm:ss");
  13. System.out.println(String.format("[%s]%s", time, msg));
  14. }
  15.  
  16. /**
  17. * 手动配置线程执行器的线程池大小
  18. */
  19. private final Executor myExecutor = Executors.newFixedThreadPool(20, new ThreadFactory() {
  20. @Override
  21. public Thread newThread(Runnable r) {
  22. Thread t = new Thread(r);
  23. //使用守护线程保证不会阻止程序的关停
  24. t.setDaemon(true);
  25. return t;
  26. }
  27. });
  28. /**
  29. * 单任务多次异步执行
  30. */
  31. @Test
  32. public void testOneFunAsync() {
  33. long start = System.nanoTime();
  34. try {
  35. printlnConsole("调用异步任务...");
  36. List<Integer> ids = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12);//准备的请求参数
  37. //ids.stream()转化为流.map()映射每个元素对应的结果.collect()把结果归纳为List;还有.filter()过滤元素.sorted()对元素进行排序.limit()获取指定数量元素;
  38. List<CompletableFuture<String>> futures = ids.stream().map(id -> CompletableFuture.supplyAsync(() -> getStudentName(id), myExecutor)).collect(Collectors.toList());
  39.  
  40. //不用并行流parallelStream()调用时就不会阻断线程执行
  41. printlnConsole("做一些其他的事情...");
  42. Thread.sleep(3000);
  43. printlnConsole("其他事情完成");
  44.  
  45. printlnConsole("获取异步任务结果:");
  46. futures.forEach(f -> {
  47. try {
  48. Object obj = f.get();
  49. printlnConsole(String.valueOf(obj));
  50. } catch (InterruptedException e) {
  51. e.printStackTrace();
  52. } catch (ExecutionException e) {
  53. e.printStackTrace();
  54. }
  55. });
  56. } catch (Exception e) {
  57. e.printStackTrace();
  58. }
  59.  
  60. long end = System.nanoTime();
  61. long time = (end - start) / 1000000;
  62. System.out.println("运行时间:" + time);
  63. }
  64.  
  65. public String getStudentName(int id) {
  66. try {
  67. Thread.sleep(2000);
  68. printlnConsole("getStudentName(" + id + ")执行完毕");
  69. } catch (InterruptedException e) {
  70. e.printStackTrace();
  71. }
  72. return "taiyonghai-" + id;
  73. }
  74. }

java 8的新特性也只做到了会用,很多深入的还不了解,还望指导谢谢,下面备份一下别人的总结我觉得挺有用的:

选择正确的线程池大小
《Java并发编程实战》中给出如下公式:

Number = NCpu * Ucpu * ( 1 + W/C)
Number : 线程数量
NCpu : 处理器核数
UCpu : 期望cpu利用率
W/C : 等待时间与计算时间比
我们这里:99%d的时间是等待商店响应 W/C = 99 ,cpu利用率期望 100% ,NCpu = 9,推断出 number = 800。但是为了避免过多的线程搞死计算机,我们选择商店数与计算值中较小的一个。

并行流与CompletableFuture
目前,我们对集合进行计算有两种方式:1.并行流 2.CompletableFuture;

1、而CompletableFuture更加的灵活,我们可以配置线程池的大小确保整体的计算不会因为等待IO而发生阻塞。

书上给出的建议如下:如果是计算密集型的操作并且没有IO推荐stream接口,因为实现简单效率也高,如果所有的线程都是计算密集型的也就没有必要创建比核数更多的线程。

2、反之,如果任务涉及到IO,网络等操作:CompletableFuture灵活性更好,因为大部分线程处于等待状态,需要让他们更加忙碌,并且再逻辑中加入异常处理可以更有效的监控是什么原因触发了等待。

参考地址:https://segmentfault.com/a/1190000012833183

多线程编程CompletableFuture与parallelStream的更多相关文章

  1. Java多线程编程基础知识汇总

    多线程简介 多任务   现代操作系统(Windows.Linux.MacOS)都可以执行多任务,多任务就是同时运行多个任务.例如在我们的计算机上,一般都同时跑着多个程序,例如浏览器,视频播放器,音乐播 ...

  2. 异步编程CompletableFuture

    多线程优化性能,串行操作并行化 串行操作 // 以下2个都是耗时操作 doBizA(); doBizB(); 修改变为并行化 new Thread(() -> doBizA()).start() ...

  3. Web Worker javascript多线程编程(一)

    什么是Web Worker? web worker 是运行在后台的 JavaScript,不占用浏览器自身线程,独立于其他脚本,可以提高应用的总体性能,并且提升用户体验. 一般来说Javascript ...

  4. Web Worker javascript多线程编程(二)

    Web Worker javascript多线程编程(一)中提到有两种Web Worker:专用线程dedicated web worker,以及共享线程shared web worker.不过主要讲 ...

  5. windows多线程编程实现 简单(1)

    内容:实现win32下的最基本多线程编程 使用函数: #CreateThread# 创建线程 HANDLE WINAPI CreateThread( LPSECURITY_ATTRIBUTES lpT ...

  6. Rust语言的多线程编程

    我写这篇短文的时候,正值Rust1.0发布不久,严格来说这是一门兼具C语言的执行效率和Java的开发效率的强大语言,它的所有权机制竟然让你无法写出线程不安全的代码,它是一门可以用来写操作系统的系统级语 ...

  7. windows多线程编程星球(一)

    以前在学校的时候,多线程这一部分是属于那种充满好奇但是又感觉很难掌握的部分.原因嘛我觉得是这玩意儿和编程语言无关,主要和操作系统的有关,所以这部分内容主要出现在讲原理的操作系统书的某一章,看完原理是懂 ...

  8. Java多线程编程核心技术---学习分享

    继承Thread类实现多线程 public class MyThread extends Thread { @Override public void run() { super.run(); Sys ...

  9. python多线程编程

    Python多线程编程中常用方法: 1.join()方法:如果一个线程或者在函数执行的过程中调用另一个线程,并且希望待其完成操作后才能执行,那么在调用线程的时就可以使用被调线程的join方法join( ...

随机推荐

  1. 移动端调试神器-vConsole

    什么是vConsole?   官方说明是一个web前端开发者面板,可用于展示console日志,方便日常开发,调试. 简单来说相当于移动版的Chrome调试控制台,就是我们在PC端常用的F12 vCo ...

  2. nginx+lua学习

    1. nginx+lua学习 1.1. 网关架构 1.2. nginx命令和信号控制 nginx -s stop 快速关闭,不管有没有正在处理的请求 nginx -s quit 优雅关闭方式,推出前完 ...

  3. 《JQuery技术内幕》读书笔记——自调用匿名函数剖析

    Javascript语言中的自调用匿名函数格式如下: (function(){ //do somethings })(); 它还有另外两种等价写法如下: //等价写法一 (function(){ // ...

  4. VueJs(11)---vue-router(命名路由,命名视图,重定向别名,路由组件传参)

    vue-router 上篇文章讲了第一篇vue-router相关文章,文章地址:VueJs(10)---vue-router(进阶1) 一.命名路由 有时候,通过一个名称来标识一个路由显得更方便一些, ...

  5. IdentityServer Resource Owner Password

    Resource Owner Password 模式需要自己实现用户名密码的校验 新增ResourceOwnerPasswordValidator实现IResourceOwnerPasswordVal ...

  6. mysql 开发基础系列10 存储引擎 InnoDB 介绍

    一. 概述: InnoDB存储引擎提供了具有提交,回滚,和崩溃恢复能力的事务安全,对比MYISAM 的存储引擎,InnoDB写的处理效率差一些并且会占用更多的磁盘空间以保留数据和索引.它的特点有如下: ...

  7. Use Generic Replacements of 1.X Framework API Classes 用泛型替换Framework 1.X版本的API类

    第一章,第一节 用泛型替换Framework 1.X版本的API类. 说起来,我是没接触过Framework 1.X版本的程序,12年毕的业(算算时间也一年多了,依旧一事无成,汗),毕业之后到公司实习 ...

  8. 翻译:赋值操作符(:=)(已提交到MariaDB官方手册)

    本文为mariadb官方手册:赋值操作符(:=)的译文. 原文:https://mariadb.com/kb/en/assignment-operator/ 我提交到MariaDB官方手册的译文:ht ...

  9. Django 系列博客(二)

    Django 系列博客(二) 前言 今天博客的内容为使用 Django 完成第一个 Django 页面,并进行一些简单页面的搭建和转跳. 命令行搭建 Django 项目 创建纯净虚拟环境 在上一篇博客 ...

  10. 前端(五)之display 总结与浮动

    前端之浮动布局.清浮动 display 总结 <!DOCTYPE html> <html> <head> <meta charset="UTF-8& ...