一、简介

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

二、代码

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

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

import org.joda.time.LocalDateTime;
import org.junit.Test;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; public class AppTest { public void printlnConsole(String msg) {
String time = new LocalDateTime().toString("yyyy-MM-dd HH:mm:ss");
System.out.println(String.format("[%s]%s", time, msg));
} /**
* 多任务单次异步执行
*/
@Test
public void testManyFunAsync() {
long start = System.nanoTime();//程序开始时间
try {
int id = 1;//模拟一个参数,如学校Id
printlnConsole("调用异步任务...");
//使用异步方式调用方法【调用时就会开始执行方法】
CompletableFuture futureClassCount = CompletableFuture.supplyAsync(() -> getClassCount(id));
CompletableFuture futureStudentCount = CompletableFuture.supplyAsync(() -> getStudentCount(id)); //do something 做了一些其他的事情超过了异步任务执行的时间
printlnConsole("做一些其他的事情...");
Thread.sleep(3000);
printlnConsole("其他事情完成"); //下面获取异步任务的结果,就会立即拿到返回值
printlnConsole("获取异步任务结果...");
Object classCount = futureClassCount.get();
//Object classCount = futureClassCount.get(2, TimeUnit.SECONDS);//可以设置超时时间,超过这个时间时将不再等待,返回异常
Object studentCount = futureStudentCount.get();
//Object studentCount = futureStudentCount.get(2, TimeUnit.SECONDS);
printlnConsole("异步任务结果获取完成"); printlnConsole("ClassCount:" + classCount);
printlnConsole("StudentCount:" + studentCount); } catch (Exception e) {
e.printStackTrace();
}
long end = System.nanoTime();//程序结束时间
long time = (end - start) / 1000000;//总耗时
System.out.println("运行时间:" + time);
} public int getClassCount(int id) {
try {
Thread.sleep(2000);
printlnConsole("getClassCount(" + id + ")执行完毕");
} catch (InterruptedException e) {
e.printStackTrace();
}
return 20;
} public int getStudentCount(int id) {
try {
Thread.sleep(1000);
printlnConsole("getStudentCount(" + id + ")执行完毕");
} catch (InterruptedException e) {
e.printStackTrace();
}
return 100;
}
}

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

package com.ysl;

import org.joda.time.LocalDateTime;
import org.junit.Test; import java.util.Arrays;
import java.util.List;
import java.util.concurrent.*;
import java.util.stream.Collectors; public class AppTest { public void printlnConsole(String msg) {
String time = new LocalDateTime().toString("yyyy-MM-dd HH:mm:ss");
System.out.println(String.format("[%s]%s", time, msg));
} /**
* 并行执行等待全部结果或等待任意结果
*/
@Test
public void testAllOfAnyOf() {
long start = System.nanoTime();
try {
printlnConsole("调用异步任务...");
List<Integer> ids = Arrays.asList(1, 3, 5);//准备的请求参数
//创建异步方法数组
CompletableFuture[] futures = ids.stream().map(id -> CompletableFuture.supplyAsync(() -> getClassName(id))).toArray(size -> new CompletableFuture[size]);
//指定该异步方法数组的子任务线程等待类型
CompletableFuture.anyOf(futures).join();//anyOf()为任意一个子任务线程执行完毕后返回
//CompletableFuture.allOf(futures).join();//allOf()为等待所有子任务线程全部执行完毕后返回 printlnConsole("做一些其他的事情...");
Thread.sleep(2000);
printlnConsole("其他事情完成"); printlnConsole("获取异步任务结果:");
for (CompletableFuture f : futures) {
//Object obj = f.getNow(1);//getNow()表示我需要立即拿到结果,如果当前的线程并未执行完成,则使用我传入的值进行任务调用,参数为无法获取结果时使用我传入的值
Object obj = f.get();//get()获取子线程运算的结果,会抛出检查到的异常
//Object obj = f.join();//join()获取子线程运算的结果,不会抛出异常
printlnConsole(String.valueOf(obj));
}
} catch (Exception e) {
e.printStackTrace();
} long end = System.nanoTime();
long time = (end - start) / 1000000;
System.out.println("运行时间:" + time);
} public String getClassName(int id) {
try {
Thread.sleep(id * 1000);
printlnConsole("getClassName(" + id + ")执行完毕");
} catch (InterruptedException e) {
e.printStackTrace();
}
return "taiyonghai-" + id;
}
}

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

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

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

import org.joda.time.LocalDateTime;
import org.junit.Test;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; public class AppTest { public void printlnConsole(String msg) {
String time = new LocalDateTime().toString("yyyy-MM-dd HH:mm:ss");
System.out.println(String.format("[%s]%s", time, msg));
} /**
* 单任务多次并行流执行
*/
@Test
public void testParallelStream() {
long start = System.nanoTime();
try {
printlnConsole("调用异步任务...");
List<Integer> ids = Arrays.asList(1, 2, 3, 4, 5);//准备的请求参数
//串行执行会等待每一个方法执行完毕后在继续执行下一个
//List<String> names = ids.stream().map(id -> getStudentName(id)).collect(Collectors.toList());
//并行执行会同时调用多个方法待全部执行完毕后一起返回(parallelStream是非线程安全的,配合collect达到线程安全,后续验证一下)
List<String> names = ids.parallelStream().map(id -> getStudentName(id)).collect(Collectors.toList());
//无论stream()或者parallelStream()调用时均会阻断线程执行
printlnConsole("做一些其他的事情...");
Thread.sleep(3000);
printlnConsole("其他事情完成"); printlnConsole("获取异步任务结果:");
names.forEach(item -> printlnConsole(item));
} catch (Exception e) {
e.printStackTrace();
}
long end = System.nanoTime();
long time = (end - start) / 1000000;
System.out.println("运行时间:" + time);
} public String getStudentName(int id) {
try {
Thread.sleep(2000);
printlnConsole("getStudentName(" + id + ")执行完毕");
} catch (InterruptedException e) {
e.printStackTrace();
}
return "taiyonghai-" + id;
}
}

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

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

import org.joda.time.LocalDateTime;
import org.junit.Test;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; public class AppTest { public void printlnConsole(String msg) {
String time = new LocalDateTime().toString("yyyy-MM-dd HH:mm:ss");
System.out.println(String.format("[%s]%s", time, msg));
} /**
* 单任务多次异步执行
*/
@Test
public void testOneFunAsync() {
long start = System.nanoTime();
try {
printlnConsole("调用异步任务...");
List<Integer> ids = Arrays.asList(1, 2, 3, 4, 5);//准备的请求参数
//ids.stream()转化为流.map()映射每个元素对应的结果.collect()把结果归纳为List;还有.filter()过滤元素.sorted()对元素进行排序.limit()获取指定数量元素;
List<CompletableFuture<String>> futures = ids.stream().map(id -> CompletableFuture.supplyAsync(() -> getStudentName(id))).collect(Collectors.toList()); //不用并行流parallelStream()调用时就不会阻断线程执行
printlnConsole("做一些其他的事情...");
Thread.sleep(3000);
printlnConsole("其他事情完成"); printlnConsole("获取异步任务结果:");
futures.forEach(f -> {
try {
Object obj = f.get();
printlnConsole(String.valueOf(obj));
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
});
}catch (Exception e){
e.printStackTrace();
} long end = System.nanoTime();
long time = (end - start) / 1000000;
System.out.println("运行时间:" + time);
} public String getStudentName(int id) {
try {
Thread.sleep(2000);
printlnConsole("getStudentName(" + id + ")执行完毕");
} catch (InterruptedException e) {
e.printStackTrace();
}
return "taiyonghai-" + id;
}
}

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

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

我们将代码做些修正

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

import org.joda.time.LocalDateTime;
import org.junit.Test; import java.util.Arrays;
import java.util.List;
import java.util.concurrent.*;
import java.util.stream.Collectors; public class AppTest { public void printlnConsole(String msg) {
String time = new LocalDateTime().toString("yyyy-MM-dd HH:mm:ss");
System.out.println(String.format("[%s]%s", time, msg));
} /**
* 手动配置线程执行器的线程池大小
*/
private final Executor myExecutor = Executors.newFixedThreadPool(20, new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
//使用守护线程保证不会阻止程序的关停
t.setDaemon(true);
return t;
}
});
/**
* 单任务多次异步执行
*/
@Test
public void testOneFunAsync() {
long start = System.nanoTime();
try {
printlnConsole("调用异步任务...");
List<Integer> ids = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12);//准备的请求参数
//ids.stream()转化为流.map()映射每个元素对应的结果.collect()把结果归纳为List;还有.filter()过滤元素.sorted()对元素进行排序.limit()获取指定数量元素;
List<CompletableFuture<String>> futures = ids.stream().map(id -> CompletableFuture.supplyAsync(() -> getStudentName(id), myExecutor)).collect(Collectors.toList()); //不用并行流parallelStream()调用时就不会阻断线程执行
printlnConsole("做一些其他的事情...");
Thread.sleep(3000);
printlnConsole("其他事情完成"); printlnConsole("获取异步任务结果:");
futures.forEach(f -> {
try {
Object obj = f.get();
printlnConsole(String.valueOf(obj));
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
});
} catch (Exception e) {
e.printStackTrace();
} long end = System.nanoTime();
long time = (end - start) / 1000000;
System.out.println("运行时间:" + time);
} public String getStudentName(int id) {
try {
Thread.sleep(2000);
printlnConsole("getStudentName(" + id + ")执行完毕");
} catch (InterruptedException e) {
e.printStackTrace();
}
return "taiyonghai-" + id;
}
}

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. (webpack系列二)webpack打包优化探索

    虽然webpack的已经升级到了webpack4,而我们目前还在使用webpack3,但其中的优化点都大同小异,升级后同样适用. 性能优化初步原则 减小代码量 减小请求数 最大化利用浏览器缓存 这三条 ...

  2. HBase之RPC调用流程简介

    首先分析hbase中对于master协议的调用: 在ConnectionImplementation的方法getKeepAliveMasterService被调用时,会通过MasterServiceS ...

  3. ES6常用特性总览

    以前看过一遍es6,今天面试时被问到了一个很简单的es6特性,竟然没回答上来,特来重温一下es6,做个总结性笔记. 一.什么是es6 es6是新版本JavaScript语言的标准,在2015年6月发布 ...

  4. Xamarin.Android 解决打开软键盘导致底部菜单上移问题

    在界面布局中有EditText控件,该控件一旦获取焦点则打开软键盘,如果布局中有底部菜单,那么底部菜单可能会被软键盘顶在其上面,看如下效果: 解决方法:在活动绑定界面之前写上下段代码即可 Window ...

  5. 深入理解OkHttp源码(二)——获取响应

    首先先看一张流程图,该图是从拆轮子系列:拆 OkHttp 中盗来的,如下: 在上一篇博客深入理解OkHttp源码(一)——提交请求中介绍到了getResponseWithInterceptorChai ...

  6. MySQL数据库事务详解

    微信公众号[程序员江湖] 作者黄小斜,斜杠青年,某985硕士,阿里 Java 研发工程师,于 2018 年秋招拿到 BAT 头条.网易.滴滴等 8 个大厂 offer,目前致力于分享这几年的学习经验. ...

  7. java~springboot~h2数据库在单元测试中的使用

    单元测试有几点要说的 事实上springboot框架是一个tdd框架,你在进行建立项目时它会同时建立一个单元测试项目,而我们的代码用例可以在这个项目里完成,对于单元测试大叔有以下几点需要说明一下: 单 ...

  8. TCP/IP 笔记 - TCP拥塞控制

    拥塞控制是TCP通信的每一方需要执行的一系列行为,这些行为有特定算法规定,用于防止网络因为大规模的通信负载而瘫痪.其基本方法是当有理由认为网络即将进入拥塞状态(或已由于拥塞而出现路由丢包情况)时减缓T ...

  9. 【干货】利用MVC5+EF6搭建博客系统(四)(下)前后台布局实现、发布博客以及展示

    二.博客系统后台布局实现 2.1.这里所用的是MVC的布局页来实现的,后台主要分为三部分:导航.菜单.主要内容 代码实现: 这里把后台单独放在一个区域里面,所以我这里建立一个admin的区域 在布局页 ...

  10. for循环中变量的作用域问题

    看下面这一行代码 for (let i = 0; i < 3; i++) { let i = 'abc' console.log(i) } // abc // abc // abc 从上面可以看 ...