点击上方“码农突围”,马上关注

这里是码农充电第一站,回复“666”,获取一份专属大礼包
真爱,请设置“星标”或点个“在看”

作者:后青春期的Keats

地址:https://www.cnblogs.com/keatsCoder/p/12934394.html

前言

在 Java7 之前,如果想要并行处理一个集合,我们需要以下几步 1. 手动分成几部分 2. 为每部分创建线程 3. 在适当的时候合并。并且还需要关注多个线程之间共享变量的修改问题。而 Java8 为我们提供了并行流,可以一键开启并行模式。是不是很酷呢?让我们来看看吧

并行流

认识和开启并行流

什么是并行流: 并行流就是将一个流的内容分成多个数据块,并用不同的线程分别处理每个不同数据块的流。例如有这么一个需求:

有一个 List 集合,而 list 中每个 apple 对象只有重量,我们也知道 apple 的单价是 5元/kg,现在需要计算出每个 apple 的单价,传统的方式是这样:

List<Apple> appleList = new ArrayList<>(); // 假装数据是从库里查出来的for (Apple apple : appleList) {apple.setPrice(5.0 * apple.getWeight() / 1000);
}

我们通过迭代器遍历 list 中的 apple 对象,完成了每个 apple 价格的计算。而这个算法的时间复杂度是 O(list.size()) 随着 list 大小的增加,耗时也会跟着线性增加。并行流

可以大大缩短这个时间。并行流处理该集合的方法如下:

appleList.parallelStream().forEach(apple -> apple.setPrice(5.0 * apple.getWeight() / 1000));

和普通流的区别是这里调用的 parallelStream() 方法。当然也可以通过 stream.parallel() 将普通流转换成并行流。并行流也能通过 sequential() 方法转换为顺序流,但要注意:流的并行和顺序转换不会对流本身做任何实际的变化,仅仅是打了个标记而已。并且在一条流水线上对流进行多次并行 / 顺序的转换,生效的是最后一次的方法调用

并行流如此方便,它的线程从那里来呢?有多少个?怎么配置呢?

并行流内部使用了默认的 ForkJoinPool 线程池。默认的线程数量就是处理器的核心数,而配置系统核心属性:java.util.concurrent.ForkJoinPool.common.parallelism 可以改变线程池大小。不过该值是全局变量。改变他会影响所有并行流。目前还无法为每个流配置专属的线程数。一般来说采用处理器核心数是不错的选择

测试并行流的性能

为了更容易的测试性能,我们在每次计算完苹果价格后,让线程睡 1s,表示在这期间执行了其他 IO 相关的操作,并输出程序执行耗时,顺序执行的耗时:

public static void main(String[] args) throws InterruptedException {List<Apple> appleList = initAppleList();Date begin = new Date();for (Apple apple : appleList) {apple.setPrice(5.0 * apple.getWeight() / 1000);Thread.sleep(1000);}Date end = new Date();log.info("苹果数量:{}个, 耗时:{}s", appleList.size(), (end.getTime() - begin.getTime()) /1000);
}

Snipaste_2020-05-21_21-49-44

并行版本

List<Apple> appleList = initAppleList();Date begin = new Date();
appleList.parallelStream().forEach(apple ->{apple.setPrice(5.0 * apple.getWeight() / 1000);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}});
Date end = new Date();
log.info("苹果数量:{}个, 耗时:{}s", appleList.size(), (end.getTime() - begin.getTime()) /1000);

耗时情况

Snipaste_2020-05-21_22-16-08

跟我们的预测一致,我的电脑是 四核I5 处理器,开启并行后四个处理器每人执行一个线程,最后 1s 完成了任务!

并行流可以随便用吗?

可拆分性影响流的速度

通过上面的测试,有的人会轻易得到一个结论:并行流很快,我们可以完全放弃 foreach/fori/iter 外部迭代,使用 Stream 提供的内部迭代来实现了。事实真的是这样吗?并行流真的如此完美吗?答案当然是否定的。大家可以复制下面的代码,在自己的电脑上测试。测试完后可以发现,并行流并不总是最快的处理方式。

  1. 对于 iterate 方法来处理的前 n 个数字来说,不管并行与否,它总是慢于循环的,非并行版本可以理解为流化操作没有循环更偏向底层导致的慢。可并行版本是为什么慢呢?这里有两个需要注意的点:

    1. iterate 生成的是装箱的对象,必须拆箱成数字才能求和

    2. 我们很难把 iterate 分成多个独立的块来并行执行

      这个问题很有意思,我们必须意识到某些流操作比其他操作更容易并行化。对于 iterate 来说,每次应用这个函数都要依赖于前一次应用的结果。因此在这种情况下,我们不仅不能有效的将流划分成小块处理。反而还因为并行化再次增加了开支。

  2. 而对于 LongStream.rangeClosed() 方法来说,就不存在 iterate 的第两个痛点了。它生成的是基本类型的值,不用拆装箱操作,另外它可以直接将要生成的数字 1 - n 拆分成 1 - n/4, 1n/4 - 2n/4, ... 3n/4 - n 这样四部分。因此并行状态下的 rangeClosed() 是快于 for 循环外部迭代的

package lambdasinaction.chap7;import java.util.stream.*;public class ParallelStreams {public static long iterativeSum(long n) {long result = 0;for (long i = 0; i <= n; i++) {result += i;}return result;}public static long sequentialSum(long n) {return Stream.iterate(1L, i -> i + 1).limit(n).reduce(Long::sum).get();}public static long parallelSum(long n) {return Stream.iterate(1L, i -> i + 1).limit(n).parallel().reduce(Long::sum).get();}public static long rangedSum(long n) {return LongStream.rangeClosed(1, n).reduce(Long::sum).getAsLong();}public static long parallelRangedSum(long n) {return LongStream.rangeClosed(1, n).parallel().reduce(Long::sum).getAsLong();}}
package lambdasinaction.chap7;import java.util.concurrent.*;
import java.util.function.*;public class ParallelStreamsHarness {public static final ForkJoinPool FORK_JOIN_POOL = new ForkJoinPool();public static void main(String[] args) {System.out.println("Iterative Sum done in: " + measurePerf(ParallelStreams::iterativeSum, 10_000_000L) + " msecs");System.out.println("Sequential Sum done in: " + measurePerf(ParallelStreams::sequentialSum, 10_000_000L) + " msecs");System.out.println("Parallel forkJoinSum done in: " + measurePerf(ParallelStreams::parallelSum, 10_000_000L) + " msecs" );System.out.println("Range forkJoinSum done in: " + measurePerf(ParallelStreams::rangedSum, 10_000_000L) + " msecs");System.out.println("Parallel range forkJoinSum done in: " + measurePerf(ParallelStreams::parallelRangedSum, 10_000_000L) + " msecs" );}public static <T, R> long measurePerf(Function<T, R> f, T input) {long fastest = Long.MAX_VALUE;for (int i = 0; i < 10; i++) {long start = System.nanoTime();R result = f.apply(input);long duration = (System.nanoTime() - start) / 1_000_000;System.out.println("Result: " + result);if (duration < fastest) fastest = duration;}return fastest;}
}

共享变量修改的问题

并行流虽然轻易的实现了多线程,但是仍未解决多线程中共享变量的修改问题。下面代码中存在共享变量 total,分别使用顺序流和并行流计算前n个自然数的和

public static long sideEffectSum(long n) {Accumulator accumulator = new Accumulator();LongStream.rangeClosed(1, n).forEach(accumulator::add);return accumulator.total;
}public static long sideEffectParallelSum(long n) {Accumulator accumulator = new Accumulator();LongStream.rangeClosed(1, n).parallel().forEach(accumulator::add);return accumulator.total;
}public static class Accumulator {private long total = 0;public void add(long value) {total += value;}
}

顺序执行每次输出的结果都是:50000005000000,而并行执行的结果却五花八门了。这是因为每次访问 totle 都会存在数据竞争,关于数据竞争的原因,大家可以看看关于 volatile 的博客。因此当代码中存在修改共享变量的操作时,是不建议使用并行流的。

并行流的使用注意

在并行流的使用上有下面几点需要注意:

  • 尽量使用 LongStream / IntStream / DoubleStream 等原始数据流代替 Stream 来处理数字,以避免频繁拆装箱带来的额外开销

  • 要考虑流的操作流水线的总计算成本,假设 N 是要操作的任务总数,Q 是每次操作的时间。N * Q 就是操作的总时间,Q 值越大就意味着使用并行流带来收益的可能性越大

    例如:前端传来几种类型的资源,需要存储到数据库。每种资源对应不同的表。我们可以视作类型数为 N,存储数据库的网络耗时 + 插入操作耗时为 Q。一般情况下网络耗时都是比较大的。因此该操作就比较适合并行处理。当然当类型数目大于核心数时,该操作的性能提升就会打一定的折扣了。更好的优化方法在日后的博客会为大家奉上

  • 对于较少的数据量,不建议使用并行流

  • 容易拆分成块的流数据,建议使用并行流

以下是一些常见的集合框架对应流的可拆分性能表

可拆分性
ArrayList 极佳
LinkedList
IntStream.range 极佳
Stream.iterate
HashSet
TreeSet
最近有有不少老铁在后台留言说,想进大厂,但是算法不好。最近我整理了一份刷题实录,这份刷题实录,也让我进了心仪的大厂。现在开放分享给大家。希望对大家有所帮助。任何的算法题,如同写作文一样,都有一些模板可以套用的。比如面试常考的DP(动态规划),难的是一些关键点是否能想清楚。比如你能写出动态转移方程,这题基本上就可以AC了。
整个刷题实录内容,包括 双子针、动态规划、二分查找、贪心算法、深度优先搜索、字符串、递归、字典树、排序、链表等相关专题内容。图文并茂,附有刷题答案源码。刷题任务的题目,是根据题目的类型来汇总的,总结了八个类别,每个类别下面也总结了5个左右的题型,帮助大家分门别类的突破,所以刷起来相对会更有重点和针对性。如果从头到尾的刷,每周按顺序刷42题,很容易让自己坚持不下来,也会觉得很枯燥。所以在制定计划的时候可以让这个计划变得更“有趣"和针对性,让它看起来更容易实现一点,才会更容易坚持。目前上述内容已打包成完整电子书,具体获取方式如下:
扫描关注 程序猿进阶 公众号;
在 程序猿进阶 公众号后台回复关键词「9999」获取下载地址。扫描关注,回复"9999"即可下载
最近热文•  突发!彭博社:马云已被限制出境!蚂蚁金服不可能上市了•  一个鹅厂码农的深圳买房笔记,7年时间从月薪1万到净资产2100万。。。•  还敢乱写代码??腾讯 Code Review 规范出炉!•  P0级重大事故:超卖了100瓶飞天茅台,整个项目组慌得一逼~•  阿里中台搞了3年,凉了?网传:副总裁玄难“背锅”,辞职创业!咸鱼放弃维护 FlutterGo!在这里,我为大家准备了一份2020年最新最全的《Java面试题及答案V3.0》,这套电子书涵盖了诸多后端技术栈的面试题和答案,相信可以帮助大家在最短的时间内复习Java后端的大多数面试题,从而拿到自己心仪的offer。截了张图,大家可以仔细查看左边的菜单栏,覆盖的知识面真的很广,而且质量都很不错。
资料获取方法
扫描下方二维码后台回复关键词:Java核心整理明天见(。・ω・。)

拥抱 Java 8 并行流:执行速度飞起 !相关推荐

  1. 拥抱 Java 8 并行流吧,让执行速度飞起!

    点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! 作者:后青春期的Keats cnblogs.com/kea ...

  2. 拥抱 Java 8 并行流:执行速度飞起

    点击上方 "程序员小乐"关注, 星标或置顶一起成长 后台回复"大礼包"有惊喜礼包! 关注订阅号「程序员小乐」,收看更多精彩内容 每日英文 I'm gratefu ...

  3. 拥抱 Java 8 并行流吧,速度飞起!

    前言 在 Java7 之前,如果想要并行处理一个集合,我们需要以下几步: 手动分成几部分 为每部分创建线程 在适当的时候合并.并且还需要关注多个线程之间共享变量的修改问题. 而 Java8 为我们提供 ...

  4. RecursiveTask和RecursiveAction的使用 以及java 8 并行流和顺序流

    转载自 https://blog.csdn.net/weixin_41404773/article/details/80733324 什么是Fork/Join框架         Fork/Join框 ...

  5. 啥?用了并行流还更慢了

    前言 Java 8给大家带来了一个非常便捷的多线程工具:并行流,一改往日Java多线程繁琐的编程规范,只需要一行代码,就可以让一个多线程跑起来,似乎让很多人忘记了被多线程支配的恐惧,这篇文章给大家分享 ...

  6. Java 8 - 正确高效的使用并行流

    文章目录 Pre 正确使用并行流,避免共享可变状态 高效使用并行流 流的数据源和可分解性 Pre Java 8 - 并行流计算入门 正确使用并行流,避免共享可变状态 错用并行流而产生错误的首要原因,就 ...

  7. java并行流 阻塞主线程_记一次使用Java8并行流导致的服务瓶颈问题排查

    一.业务背景# 二.服务架构# 服务使用线程池对请求进行业务处理,corePoolSize=32,maximumPoolSize=128. 三.问题描述# 服务部署到测试环境,将线上流量通过tcp-c ...

  8. java流有什么用_在Java中,流比循环有什么优势?

    达令说 有趣的是,面试问题只问优点,而不问缺点,因为两者都有.流是一种更具声明性的样式.或更富有表现力的风格.它可以被认为是更好的声明你的代码的意图,而不是描述如何,它的完成: return peop ...

  9. Java8 并行流(parallelStream)原理分析及注意事项

    文章目录 前言 一.parallelStream是什么 二.parallelStream原理分析 1.Fork/Join框架 1.1 work-stealing(工作窃取算法) 1.2 常用方法 2. ...

最新文章

  1. Pandas循环提速7万多倍!Python数据分析攻略
  2. 2022年全球及中国光纤馈通件行业发展建议与十四五规划动向展望报告
  3. JavaScript 中的对象拷贝(深拷贝、浅拷贝)
  4. zabbix分布式监控环境完全编译安装部署
  5. SAP云平台,区块链,超级账本和智能合约
  6. arrays.sort(._Arrays.hashCode(Object [])与Objects.hash(Object…)
  7. localStorage封装借口store.js的使用
  8. 报告显示Q2 Android平板电脑全球市场份额达67%
  9. Serverless 在大规模数据处理中的实践
  10. 不搞虚的!快速把你拉入Docker 的门里
  11. kd树 python实现_kd树 寻找k近邻算法 python实现
  12. Linux系统简介、安装RHEL7系统、RHEL7基本操作
  13. 什么是Power BI?
  14. 分享一点关于安装、使用达梦数据库的愚见
  15. 关机闹钟原理,Android和Iphone关机闹钟
  16. Notepad++ 7.6版本 安装hexeditor最新详细版本(小白版)
  17. python,人工智能,水果识别
  18. 高等数学--导数、偏导数、梯度简介
  19. Git生成密钥(ssh)
  20. AI生成肖像画,精细到毛发!北大校友最新研究收割2.8k星标,还登上了ICPR 2020...

热门文章

  1. 小程序 英语学习小助手java
  2. 第十七届全国大学生智能汽车华南赛区竞赛 - 流程册(文档)
  3. 暴利:网友惊爆域名注册底价
  4. XDOJ-1098-突击数论前的xry111(筛选法求欧拉函数)
  5. 2021年P气瓶充装考试内容及P气瓶充装最新解析
  6. 电脑卡顿就重装系统?——可能问题只是在显卡驱动
  7. 反射---getattr,hasattr,setattr,delattr加插播enumerate
  8. ip广播系统服务器软件,【网络广播服务器软件IP网络广播软件数字广播软件】 - 太平洋安防网...
  9. 新媒体监管要与时俱进
  10. 首先贴一张美图看看,