java8-stream 4 聊聊串行与并行

在进行接下来的话题前,我们来谈谈并行,串行的问题。

我们本节要弄明白的问题是,串行和并行,执行的流程是什么样的。
不同的中间操作(运功)会对流程有什么样的影响。

说真心的我真的不想大段大段贴代码。但是为了让各位看官能明白我在做什么,我实在是没有其他什么特别好的办法了。

串行 一个线程做完所有事情
优点:不存在什么线程安全问题,保证处理的先后顺序。
缺点:速度比较慢。

串行 演示.gif

来看一个简单的例子,我需要将集合中的1,2,3,4,5。每个元素成先乘以10,然后再乘以10。将结果写到一个list。
当然进行这个例子,不是无聊到每个数字乘以100。而是要向大家演示串行时,stream是如何工作的。

public class Test20181211 {

    @Test
    public void test1() {

        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
        List<Integer> collect = list.stream()
                .map(this::map1)
                .map(this::map2)
                .collect(Collectors.toList());
        System.out.println(collect);

    }

    private Integer map1(Integer i) {
        int r = i * 10;
        System.out.println("线程:" + Thread.currentThread().getName() + "\t方法 map1\t" + "收到:" + i + "\t" + "输出:" + r);
        return r;
    }

    private Integer map2(Integer i) {
        int r = i * 10;
        System.out.println("线程:" + Thread.currentThread().getName() + "\t方法 map2\t" + "收到:" + i + "\t" + "输出:" + r);
        return r;
    }

}
线程:main 方法 map1 收到:1    输出:10
线程:main 方法 map2 收到:10   输出:100
线程:main 方法 map1 收到:2    输出:20
线程:main 方法 map2 收到:20   输出:200
线程:main 方法 map1 收到:3    输出:30
线程:main 方法 map2 收到:30   输出:300
线程:main 方法 map1 收到:4    输出:40
线程:main 方法 map2 收到:40   输出:400
线程:main 方法 map1 收到:5    输出:50
线程:main 方法 map2 收到:50   输出:500
[100, 200, 300, 400, 500]

不管运行几次,我们都会得到相同的结果。
从结果来看,main线程的处理过程是每次拿一个元素,然后顺序的进行一遍流程,做完以后进行下一个元素。最后将结果收集起来。

提问:如果是如下的处理过程。stream的处理过程是A还是B

.map(this::map1)
.map(this::map2)
.sorted((o1, o2) -> o2-o1)
.map(this::map3)
.map(this::map4)

A 获得元素->map1->map2->排序->map3->map4->获得元素->map1->map2->排序->map3->map4->循环到无元素->收集
B 获得元素->map1->map2->获得元素->map1->map2->循环到无元素->排序->获得元素->map3->map4->获得元素->map3->map4->循环到无元素->收集

答案是B

无需等待上游类型 ##### filter | map | flatMap | peek | peek |
需要等待上游类型 ##### distinct | limit | sorted
我们将中间操作分为两种类别。
需要等待上游类型的集中操作,都是需要上游流程处理结束以后,根据结果才能做出操作的。

public class Test20181211 {

    @Test
    public void test1() {

        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
        List<Integer> collect = list.stream()
                .map(this::map1)
                .map(this::map2)
                .sorted((o1, o2) -> o2 - o1)
                .map(this::map3)
                .map(this::map4)
                .sorted()
                .limit(2)
                .collect(Collectors.toList());
        System.out.println(collect);

    }

    private Integer map1(Integer i) {
        int r = i * 10;
        System.out.println("线程:" + Thread.currentThread().getName() + "\t方法 map1\t" + "收到:" + i + "\t" + "输出:" + r);
        return r;
    }

    private Integer map2(Integer i) {
        int r = i * 10;
        System.out.println("线程:" + Thread.currentThread().getName() + "\t方法 map2\t" + "收到:" + i + "\t" + "输出:" + r);
        return r;
    }

    private Integer map3(Integer i) {
        int r = i * 10;
        System.out.println("线程:" + Thread.currentThread().getName() + "\t方法 map3\t" + "收到:" + i + "\t" + "输出:" + r);
        return r;
    }

    private Integer map4(Integer i) {
        int r = i * 10;
        System.out.println("线程:" + Thread.currentThread().getName() + "\t方法 map4\t" + "收到:" + i + "\t" + "输出:" + r);
        return r;
    }

}

线程:main 方法 map1 收到:1    输出:10
线程:main 方法 map2 收到:10   输出:100
线程:main 方法 map1 收到:2    输出:20
线程:main 方法 map2 收到:20   输出:200
线程:main 方法 map1 收到:3    输出:30
线程:main 方法 map2 收到:30   输出:300
线程:main 方法 map1 收到:4    输出:40
线程:main 方法 map2 收到:40   输出:400
线程:main 方法 map1 收到:5    输出:50
线程:main 方法 map2 收到:50   输出:500
这里做了逆序排序
线程:main 方法 map3 收到:500  输出:5000
线程:main 方法 map4 收到:5000 输出:50000
线程:main 方法 map3 收到:400  输出:4000
线程:main 方法 map4 收到:4000 输出:40000
线程:main 方法 map3 收到:300  输出:3000
线程:main 方法 map4 收到:3000 输出:30000
线程:main 方法 map3 收到:200  输出:2000
线程:main 方法 map4 收到:2000 输出:20000
线程:main 方法 map3 收到:100  输出:1000
线程:main 方法 map4 收到:1000 输出:10000
这里做了正序排序 还有限制
[10000, 20000]

所以最终应该是这个样子


串行 综合演示.gif

并行 多个线程一起做
优点:处理速度快

缺点:线程安全处理不得当会引发灾难。

并行 演示.gif
public class Test20181211 {

    @Test
    public void test1() {

        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
//注意 注意 注意 我只改动了这里 生成了一个并行流
        List<Integer> collect = list.parallelStream()
                .map(this::map1)
                .map(this::map2)
                .sorted((o1, o2) -> o2 - o1)
                .map(this::map3)
                .map(this::map4)
                .sorted()
                .limit(2)
                .collect(Collectors.toList());
        System.out.println(collect);

    }

    private Integer map1(Integer i) {
        int r = i * 10;
        System.out.println("线程:" + Thread.currentThread().getName() + "\t方法 map1\t" + "收到:" + i + "\t" + "输出:" + r);
        return r;
    }

    private Integer map2(Integer i) {
        int r = i * 10;
        System.out.println("线程:" + Thread.currentThread().getName() + "\t方法 map2\t" + "收到:" + i + "\t" + "输出:" + r);
        return r;
    }

    private Integer map3(Integer i) {
        int r = i * 10;
        System.out.println("线程:" + Thread.currentThread().getName() + "\t方法 map3\t" + "收到:" + i + "\t" + "输出:" + r);
        return r;
    }

    private Integer map4(Integer i) {
        int r = i * 10;
        System.out.println("线程:" + Thread.currentThread().getName() + "\t方法 map4\t" + "收到:" + i + "\t" + "输出:" + r);
        return r;
    }

}

每次输出的结果,就没有那么固定了。有的时候线程1先做完了工作,有的时候线程2先做完了工作。

线程:main                             方法 map1 收到:5            输出:50
线程:ForkJoinPool.commonPool-worker-4 方法 map1 收到:4            输出:40
线程:ForkJoinPool.commonPool-worker-1 方法 map1 收到:2            输出:20
线程:ForkJoinPool.commonPool-worker-3 方法 map1 收到:1            输出:10
线程:ForkJoinPool.commonPool-worker-3 方法 map2 收到:10           输出:100
线程:ForkJoinPool.commonPool-worker-2 方法 map1 收到:3            输出:30
线程:ForkJoinPool.commonPool-worker-1 方法 map2 收到:20           输出:200
线程:ForkJoinPool.commonPool-worker-4 方法 map2 收到:40           输出:400
线程:main                             方法 map2 收到:50           输出:500
线程:ForkJoinPool.commonPool-worker-2 方法 map2 收到:30           输出:300
在这个位置 sorted 大家等排序完成以后继续抢工作,排序工作开始的前提是上游stream里面的内容都处理结束了。
线程:main                             方法 map3 收到:100          输出:1000
线程:ForkJoinPool.commonPool-worker-3 方法 map3 收到:200          输出:2000
线程:ForkJoinPool.commonPool-worker-3 方法 map4 收到:2000         输出:20000
线程:ForkJoinPool.commonPool-worker-2 方法 map3 收到:400          输出:4000
线程:ForkJoinPool.commonPool-worker-2 方法 map4 收到:4000         输出:40000
线程:ForkJoinPool.commonPool-worker-1 方法 map3 收到:500          输出:5000
线程:ForkJoinPool.commonPool-worker-4 方法 map3 收到:300          输出:3000
线程:ForkJoinPool.commonPool-worker-1 方法 map4 收到:5000         输出:50000
线程:main                             方法 map4 收到:1000         输出:10000
线程:ForkJoinPool.commonPool-worker-4 方法 map4 收到:3000         输出:30000
在这个位置 sorted 大家等排序完成以后继续抢工作,排序工作开始的前提是上游stream里面的内容都处理结束了。
排序结束后,limit前两个,输出到下list
[10000, 20000]

我们可以看到,我们自己在没有创建线程的情况下,仅仅通过parallelStream 获取并行流。就开启了并行处理能力。用起来十分的方便。通过线程名字,我们也能发现
是ForkJoinPool线程池中的
不同commonPool-worker-*来帮我们做了并行的工作。

下一节中 我们会讲讲,错误使用并行流造成的影响。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,045评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,114评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,120评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,902评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,828评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,132评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,590评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,258评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,408评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,335评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,385评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,068评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,660评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,747评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,967评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,406评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,970评论 2 341

推荐阅读更多精彩内容