大家好,又见面了,我是你们的朋友全栈君。
Stream 和 parallelStream
一.什么是Stream?
Stream 是在 Java8 新增的特性,普遍称其为流;它不是数据结构也不存放任何数据,其主要用于集合的逻辑处理。
二.和Iterator的区别
Iterator 做为迭代器,其按照一定的顺序迭代遍历集合中的每一个元素,并且对每个元素进行指定的操作。而 Stream 在此基础上还可以将这种操作并行化,利用多核处理器的优势快速处理集合(集合的数据会分成多个段,由多个线程处理)。
Stream 的数据源可以有无限多个。
三.Stream的使用
在使用Stream之前,建义先理解接口化编程,Stream将完全依赖于接口化编程方式。接下来我们以“打印集合中的每一个元素”为例,了解一下 Stream 的使用。
例3.1
代码语言:javascript复制List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
numbers.stream().forEach(num->System.out.println(num));
输出:1 2 3 4 5 6 7 8 9
由以上的列子可以看出,Stream 的遍历方式和结果与 Iterator 没什么差别,这是因为Stream的默认遍历是和迭代器相同的,保证以往使用迭代器的地方可以方便的改写为 Stream。
Stream 的强大之处在于其通过简单的链式编程,使得它可以方便地对遍历处理后的数据进行再处理。我们以“对集合中的数字加1,并转换成字符串”为例进行演示。
例3.2
代码语言:javascript复制List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);`
`List<String> strs = numbers.stream()`
`.map(num->Integer.toString( num)).collect(Collectors.toList());
其中map()方法遍历处理每一个元素,并且返回一个新的Stream,随后collect方法将操作后的Stream解析为List。
Stream还提供了非常多的操作,如filter()过滤、skip()偏移等等,想要了解更多可以去翻阅JDK1.8手册或者相关资料。
四.并行流parallelStream
parallelStream提供了流的并行处理,它是Stream的另一重要特性,其底层使用Fork/Join框架实现。简单理解就是多线程异步任务的一种实现。
我们用例3.1中的示例演示一下parallelStream的使用。
例4.1
代码语言:javascript复制List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
numbers.parallelStream().forEach(num->System.out.println(num));
输出:3 4 2 6 7 9 8 1 5
我们发现,使用parallelStream后,结果并不按照集合原有顺序输出。为了进一步证明该操作是并行的,我们打印出线程信息。
例4.2
代码语言:javascript复制 List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
numbers.parallelStream() .forEach(num-
>System.out.println(Thread.currentThread().getName() ">>" num));
输出:
代码语言:javascript复制main>>6
ForkJoinPool.commonPool-worker-2>>8
main>>5 ForkJoinPool.commonPool-worker-2>>9
ForkJoinPool.commonPool-worker-1>>3
ForkJoinPool.commonPool-worker-3>>2
ForkJoinPool.commonPool-worker-1>>1
ForkJoinPool.commonPool-worker-2>>7
main>>4
通过例4.2可以确信parallelStream是利用多线程进行的,这可以很大程度简化我们使用并发操作。
我们可以通过虚拟机启动参数
-Djava.util.concurrent.ForkJoinPool.common.parallelism=N
来设置worker的数量。
五.并行流的陷阱
5.1.线程安全
由于并行流使用多线程,则一切线程安全问题都应该是需要考虑的问题,如:资源竞争、死锁、事务、可见性等等。
5.2.线程消费
在虚拟机启动时,我们指定了worker线程的数量,整个程序的生命周期都将使用这些工作线程;这必然存在任务生产和消费的问题,如果某个生产者生产了许多重量级的任务(耗时很长),那么其他任务毫无疑问将会没有工作线程可用;更可怕的事情是这些工作线程正在进行IO阻塞。
本应利用并行加速处理的业务,因为工作者不够反而会额外增加处理时间,使得系统性能在某一时刻大打折扣。而且这一类问题往往是很难排查的。我们并不知道一个重量级项目中的哪一个框架、哪一个模块在使用并行流。
接下来我们对这个问题进行演示:
例5.1
输出:
通过示例我们会发现,第一个并行流率先获得worker线程的使用权,第二个并行流变为串行;直到第14行,第一个并行流处理完毕,第二个并行流获取worker线程,开始并行处理。
小结:
串行流:适合存在线程安全问题、阻塞任务、重量级任务,以及需要使用同一事务的逻辑。
并行流:适合没有线程安全问题、较单纯的数据处理任务。
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/161782.html原文链接:https://javaforall.cn