解决JDK8的ParallelStream遍历无序的问题

JDK8的ParallelStream遍历无序

ParallelStream其实就是一个并行执行的流

它通过默认的ForkJoinPool,可能提高你的多线程任务的速度.

Stream具有平行处理能力,处理的过程会分而治之,也就是将一个大任务切分成多个小任务,这表示每个任务都是一个操作,因此像以下的程式片段:

List
       list = Arrays.asList(1, 2, 3, 4, 5);
list.parallelStream().forEach(out::println);

你得到的展示顺序不一定会是1、2、3、4、5,而可能是任意的顺序,就forEach()这个操作來讲,如果平行处理时,希望最后顺序是按照原来Stream的数据顺序,那可以调用forEachOrdered()。

List
       list = Arrays.asList(1, 2, 3, 4, 5);
list.parallelStream().forEachOrdered(out::println);

parallelStream进行遍历的坑,以及如何进行避免异步操作中出现的问题

Java8 已经很久了,现在都已经Java12版本了.

我所在的上家公司,在写代码时候推荐使用lambad来进行操作遍历集合

也就是像下面一样

List<Integer> list = new ArrayList<>();
for (int j = 0; j < 1000; j++) {
list.add(j);
}
list.stream().forEach(value -> {
System.out.println(value);
});

这种效率其实和传统上的使用foreach以及for循环遍历效果差不多,因为点开forEach方法会发现内部其实使用的是下面的方法进行对集合遍历的

内部其实使用的还是for进行遍历,所以两者相比较其实没有什么效率的差异的,当然这也会由于每个公司编程习惯不一样,有的人更喜欢传统上的for进行遍历

因为上面的遍历方式不会对效率有什么提升, 所以由此还有一种方式就是

parallelStream()
List<Integer> list = new ArrayList<>();
for (int j = 0; j < 1000; j++) {
list.add(j);
}
list.parallelStream().forEach(value -> {
System.out.println(value);
});

上面的方法其实就是异步的,

这种遍历方式因为是异步遍历,会产生一种情况,就是遍历的顺序是无序的,当然也有相应的好处就是,遍历速度会快,当对生成结果不考虑排序问题而且数据量比较大的时候可以使用.

但是,有利自然有弊,因为异步的所以需要考虑线程的问题,就是生成的结果真的是你想要的么?

以下面的例子来运行一段代码:

public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
for (int j = 0; j < 1000; j++) {
list.add(j);
}
System.out.println("最开始生成的集合长度:"+list.size());
//parallelStream遍历数据的时候会产生丢失的问题
for (int i = 0; i < 10 ; i++) {

List<Integer> parseList = new ArrayList<>();
list.parallelStream().forEach(integer -> {
parseList.add(integer);
});
System.out.println("每次遍历的集合长度:"+ parseList.size());
}
}

我首先创建了一个1000长度的集合,之后对这个集合使用多次遍历,然而呢,会发现,最后遍历的集合少数据,并且会在多次重复遍历的时候数组越界..

因为这种情况,之前工作使用parallelStream出现过2次问题, 我一直以为是使用parallelStream本身不够很安全导致的.实际上今天整理这篇博文突然才发现这个问题,就是遍历的结果转为的list是线程安全的么?

其实当正常进行遍历的时候, 可以对遍历出的结果核对,实际上每次遍历出的结果,仍然是与原来生成的结果一致的.

所以这边只能将锅甩在接收这些数据的list上面了

这个时候就需要对list进行包装

List<Integer> synchronizedList = Collections.synchronizedList(parseList);

这会在看下修改后的代码以及结果

public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
for (int j = 0; j < 1000; j++) {
list.add(j);
}
System.out.println("最开始生成的集合长度:"+list.size());
//parallelStream遍历数据的时候会产生丢失的问题
for (int i = 0; i < 10 ; i++) {

List<Integer> parseList = new ArrayList<>();
List<Integer> synchronizedList = Collections.synchronizedList(parseList);
list.parallelStream().forEach(integer -> {
synchronizedList.add(integer);
});
System.out.println("每次遍历的集合长度:"+ synchronizedList.size());
}
}

这样每次遍历的结果也都是一样的,而且速度也会由于异步的会比之前效率提升好多

同样的如何创建线程安全的set,map也就可以进行相应的包装,这样就避免了使用会出新一些明明感觉对,确和自己想要的结果不一致的bug

同理使用parallelStream用StringBuffer 而不适用StringBuilder,因为前者是线程安全的

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • 关于Java8 parallelStream并发安全的深入讲解

    背景 Java8的stream接口极大地减少了for循环写法的复杂性,stream提供了map/reduce/collect等一系列聚合接口,还支持并发操作:parallelStream. 在爬虫开发过程中,经常会遇到遍历一个很大的集合做重复的操作,这时候如果使用串行执行会相当耗时,因此一般会采用多线程来提速.Java8的paralleStream用fork/join框架提供了并发执行能力.但是如果使用不当,很容易陷入误区. Java8的paralleStream是线程安全的吗 一个简单的例子,

  • 通过实例解析java8中的parallelStream

    这篇文章主要介绍了通过实例解析java8中的parallelStream,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 about Stream 什么是流? Stream是java8中新增加的一个特性,被java猿统称为流. Stream 不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的 Iterator.原始版本的 Iterator,用户只能显式地一个一个遍历元素并对其执行某些操作:高级版本的 Stream

  • 深入浅出Java8中parallelStream的使用

    about Stream 什么是流? Stream是java8中新增加的一个特性,被java猿统称为流. Stream 不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的 Iterator.原始版本的 Iterator,用户只能显式地一个一个遍历元素并对其执行某些操作:高级版本的 Stream,用户只要给出需要对其包含的元素执行什么操作,比如 "过滤掉长度大于 10 的字符串"."获取每个字符串的首字母"等,Stream 会隐式地在

  • 深入解析Jdk8中Stream流的使用让你脱离for循环

    学习要求: 知道一点儿函数式接口和Lambda表达式的基础知识,有利于更好的学习. 1.先体验一下Stream的好处 需求:给你一个ArrayList用来保存学生的成绩,让你打印出其中大于60的成绩. public static void main(String[] args) { ArrayList<Integer> arrList = new ArrayList<>(); for (int i = 0; i < 100; i++) { arrList.add((int)

  • 解决JDK8的ParallelStream遍历无序的问题

    JDK8的ParallelStream遍历无序 ParallelStream其实就是一个并行执行的流 它通过默认的ForkJoinPool,可能提高你的多线程任务的速度. Stream具有平行处理能力,处理的过程会分而治之,也就是将一个大任务切分成多个小任务,这表示每个任务都是一个操作,因此像以下的程式片段: List list = Arrays.asList(1, 2, 3, 4, 5); list.parallelStream().forEach(out::println); 你得到的展示顺

  • 解决Golang map range遍历结果不稳定问题

    闲言少叙,本文主要是想介绍一个Golang开发常见的一个问题.然而,此问题对于初学者来说却经常容易陷入坑中. 问题 我在写一段代码时,使用了Golang的map数据结构,目的是想用map缓存计数结果.简单来说map的键也是整型的,且以递增顺序存储.我的最初想法是,在统计结束后,按照map中存储的键有序输出值.可是,当我运行程序时,结果并不是我想要的,而且有一定概率运行结果不同. 问题代码 func sortByBits(arr []int) []int { var bitmap = make(m

  • Python数据结构与算法之链表,无序链表详解

    目录 我们首先来构造节点. 节点(Node)的类构建完毕后,接下来我们开始构建整个链表(LinkList)的类. 那么我们还需要一个方法来判断链表头的指向. 接下来我们构建链表节点的添加方法. 实现length方法(计算链表中节点的个数/链表长度) 实现search方法(搜索链表中的某个节点) 实现remove方法(移除链表中的某个节点) 代码汇总 总结 链表是一系列元素的集合,这些元素的内存是散乱的. 无序链表则是一系列逻辑无序元素的集合,只是通过链表数据结构连接起来.虽然这些元素整体来看是散

  • 详解常用查找数据结构及算法(Python实现)

    一.基本概念 查找(Searching)就是根据给定的某个值,在查找表中确定一个其关键字等于给定值的数据元素(或记录). 查找表(Search Table):由同一类型的数据元素(或记录)构成的集合 关键字(Key):数据元素中某个数据项的值,又称为键值. 主键(Primary Key):可唯一地标识某个数据元素或记录的关键字. 查找表按照操作方式可分为: 静态查找表(Static Search Table):只做查找操作的查找表.它的主要操作是: 查询某个"特定的"数据元素是否在表中

  • Python实现七大查找算法的示例代码

    查找算法 -- 简介 查找(Searching)就是根据给定的某个值,在查找表中确定一个其关键字等于给定值的数据元素.     查找表(Search Table):由同一类型的数据元素构成的集合     关键字(Key):数据元素中某个数据项的值,又称为键值     主键(Primary Key):可唯一的标识某个数据元素或记录的关键字 查找表按照操作方式可分为:         1.静态查找表(Static Search Table):只做查找操作的查找表.它的主要操作是:         ①

  • Objective-C中NSNumber与NSDictionary的用法简介

    NSNumber的常用方法 在Objective-c中有int的数据类型,那为什么还要使用数字对象NSNumber?这是因为很多类(如NSArray)都要求使用对象,而int不是对象. NSNumber就是数字对象我们可以使用NSNumber对象来创建和初始化不同类型的数字对象. NSNumber    + (NSNumber *)numberWithInt:(int)value;    + (NSNumber *)numberWithDouble:(double)value;    - (in

  • Java常用排序算法及性能测试集合

    现在再回过头理解,结合自己的体会, 选用最佳的方式描述这些算法,以方便理解它们的工作原理和程序设计技巧.本文适合做java面试准备的材料阅读. 先附上一个测试报告: Array length: 20000bubbleSort : 766 msbubbleSortAdvanced : 662 msbubbleSortAdvanced2 : 647 msselectSort : 252 msinsertSort : 218 msinsertSortAdvanced : 127 msinsertSor

  • Android仿微信照片选择器实现预览查看图片

    好了下面进入正题,我们先看一下实现效果吧: 下面来介绍一下代码: 本思路就是: 1.先到手机中扫描jpeg和png的图片 2.获取导图片的路径和图片的父路径名也就是文件夹名 3.将图片路径和文件夹名分别添加导数据源中 4.数据源有了就是显示了,文件夹显示是利用的popwindow,而图片显示则是GridView 看一下具体代码: 首先开启一个线程去扫描图片 /** * 利用ContentProvider扫描手机中的图片,此方法在运行在子线程中 完成图片的扫描,最终获得jpg最多的那个文件夹 */

随机推荐