多线程编程CompletableFuture与parallelStream
一、简介
平常在页面中我们会使用异步调用$.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的更多相关文章
- Java多线程编程基础知识汇总
多线程简介 多任务 现代操作系统(Windows.Linux.MacOS)都可以执行多任务,多任务就是同时运行多个任务.例如在我们的计算机上,一般都同时跑着多个程序,例如浏览器,视频播放器,音乐播 ...
- 异步编程CompletableFuture
多线程优化性能,串行操作并行化 串行操作 // 以下2个都是耗时操作 doBizA(); doBizB(); 修改变为并行化 new Thread(() -> doBizA()).start() ...
- Web Worker javascript多线程编程(一)
什么是Web Worker? web worker 是运行在后台的 JavaScript,不占用浏览器自身线程,独立于其他脚本,可以提高应用的总体性能,并且提升用户体验. 一般来说Javascript ...
- Web Worker javascript多线程编程(二)
Web Worker javascript多线程编程(一)中提到有两种Web Worker:专用线程dedicated web worker,以及共享线程shared web worker.不过主要讲 ...
- windows多线程编程实现 简单(1)
内容:实现win32下的最基本多线程编程 使用函数: #CreateThread# 创建线程 HANDLE WINAPI CreateThread( LPSECURITY_ATTRIBUTES lpT ...
- Rust语言的多线程编程
我写这篇短文的时候,正值Rust1.0发布不久,严格来说这是一门兼具C语言的执行效率和Java的开发效率的强大语言,它的所有权机制竟然让你无法写出线程不安全的代码,它是一门可以用来写操作系统的系统级语 ...
- windows多线程编程星球(一)
以前在学校的时候,多线程这一部分是属于那种充满好奇但是又感觉很难掌握的部分.原因嘛我觉得是这玩意儿和编程语言无关,主要和操作系统的有关,所以这部分内容主要出现在讲原理的操作系统书的某一章,看完原理是懂 ...
- Java多线程编程核心技术---学习分享
继承Thread类实现多线程 public class MyThread extends Thread { @Override public void run() { super.run(); Sys ...
- python多线程编程
Python多线程编程中常用方法: 1.join()方法:如果一个线程或者在函数执行的过程中调用另一个线程,并且希望待其完成操作后才能执行,那么在调用线程的时就可以使用被调线程的join方法join( ...
随机推荐
- 移动端点击事件300ms延迟问题解决方案——fastclick.js
移动端点击事件300ms延迟的问题由来已久,如下截图 下面截图来自原文:https://www.jianshu.com/p/6e2b68a93c88 网上关于300ms延迟问题的解决方法,大致分为 3 ...
- Python开发端口扫描器
首先是最常用的端口扫描器: 虽说有nmap等强大的工具,不过如果由于条件限制无法安装Nmap呢? 我这个脚本写的比较简单,默认扫描1-65535全部的端口 实际的话,可以根据需要自己修改脚本来实现定制 ...
- PHP的会话控制
页面数据的作用域: 当前页共享数据:变量.常量 两个页面间传递数据:get,post 跟踪用户的多页面数据共享(会话):session.cookie 全局数据共享:文件.数据库.memcached C ...
- JavaScript几个作用域问题
1. var a = 0 ; function f(){ a = 1; console.log(a); //全局变量a } console.log(a); //全局变量a f(); 输出结果为:1 1 ...
- 通过Microsoft Learn进行学习以提升技能
通过 Microsoft Learn,可以免费而且轻松有趣地学习 Microsoft 技术. Microsoft Learn的与众不同 借助 Microsoft Learn,任何人都能按自己的学习计划 ...
- IDEA添加Git项目
1.进入主页面IntelliJ IDEA (如果不知道如何进入主页面,在加载项目是点击“Cancel”按钮 ) 2.点击“Check out from Version Control” ,选择“Git ...
- #2 Python面向对象(一)
前言 对于萌新来说,面向对象,这是一个很陌生的词语.如果你接触过Linux,你一定知道“一切皆文件”,也就是说,在Linux中所有都是文件,鼠标是文件.键盘是文件.目录是文件等等一切都是文件:Pyth ...
- Jenkins凭证及任务演示-pipeline(二)--技术流ken
Jenkins前言 在上一篇博客<Jenkins持续集成介绍及插件安装版本更新演示(一)--技术流ken>中已经详细介绍了jenkins的插件安装以版本更新等,本篇博客将再深入探究jenk ...
- 响应者链UIResponder-扩大UIButton的点击范围
在开发中,我们经常看到有按钮等的点击,会出现响应事件.按钮->view->ViewController->UIWindow->UIApplication,这就形成了一个响应链. ...
- [转]Angular4首页加载慢优化之路
本文转自:https://blog.csdn.net/itest_2016/article/details/80048398 Angular是一个比较完善的前端MVC框架,包含了模板,数据双向绑定,路 ...