Java8之Stream流(四)并行流

Java8之Stream流(一)基礎(chǔ)體驗
Java8之Stream流(二)關(guān)鍵知識點
Java8之Stream流(三)縮減操作
Java8之Stream流(五)映射流
Java8之Stream流(六)收集
Java8之Stream流(七)流與迭代器

隨著對流API認識的慢慢深入生巡,本章我們要討論的知識點是流API里面的并行流了糟描。在開始討論并行流之前,我先引發(fā)一下大家的思考,就你看到這篇文章的時間疆柔,你們是不是經(jīng)常聽到晴音,Intel i7 CPU什么8核16線程灰粮,什么Android手機8核4GB這種消息哎榴,既然我們是處于一個多核處理器的時代寝杖,你們有沒有想過并行地操作數(shù)組和集合框架违施,從而高速地執(zhí)行我們對數(shù)組或者集合的一些操作呢?或許你有想過這個問題瑟幕,但是因為并行編程比較復(fù)雜磕蒲,所以這些想法還停留在你的腦海當中留潦,又或者你已經(jīng)在路上了,反正你們就是最棒的(我他媽都這么夸你們了辣往,就不能點個喜歡兔院?),不管如何站削,在你看到這一篇文章的時候坊萝,我將帶你走向并行地操作數(shù)組或者集合,當然是使用我們的并行流知識啦许起。

并行流

并行編程可謂是十分復(fù)雜并且很容易出錯的十偶,這估計就是我們絕大部分人的攔腳石。剛好Stream流庫給我們解決了這個問題园细,在流API庫里面提供了輕松可靠的并行操作惦积。要想并行處理流相當簡單,只需要使用一個并行流就可以了猛频。如第二篇文章中提到的那樣狮崩,我們獲取一個并行流是非常簡單的,只需要對流調(diào)用一下parallel()就可以獲取到一個并行流了(什么你居然不知道鹿寻?那么多人看了我的文章睦柴,估計你要被他們甩開幾條街了,趕緊回去看吧毡熏。)坦敌,第二種方式就更加簡單了,我們可以使用Collection接口提供給我們parallelStream(),也是可以獲取到一個并行流的招刹。當然恬试,并行操作肯定是需要環(huán)境支持的,你搞了一臺一核一線程的小霸王疯暑,來跑我的高大上并行流,我也只能慢慢來了哑舒。如果你不是小霸王妇拯,那我們可以開始這節(jié)課的實戰(zhàn)了,先拿上一篇的例子來改一下先洗鸵,如果你不認真觀察,你都找不出他們的不同之處:

public class Main {

    public static void main(String[] args) {
        learnStream();
    }



    private static void learnStream() {
        List<Integer> lists = new ArrayList<>();
        lists.add(1);
        lists.add(2);
        lists.add(3);
        lists.add(4);
        lists.add(5);
        lists.add(6);

        Optional<Integer> sum = lists.parallelStream().reduce((a, b) -> a + b);//這里把stream()換成了parallelStream()
        if (sum.isPresent()) System.out.println("list的總和為:" + sum.get());//21
        //<====> lists.stream().reduce((a, b) -> a + b).ifPresent(System.out::println);

        Integer sum2 = lists.stream().reduce(0, (a, b) -> a + b);//21
        System.out.println("list的總和為:" + sum2);

        Optional<Integer> product = lists.stream().reduce((a, b) -> a * b);
        if (product.isPresent()) System.out.println("list的積為:" + product.get());//720

        Integer product2 = lists.parallelStream().reduce(1, (a, b) -> a * b);//這里把stream()換成了parallelStream()
        System.out.println("list的積為:" + product2);//720
    }
}

得到結(jié)果和上一篇文章的一模一樣膘滨。但是因為乘法和加法操作是可以發(fā)生在不同的線程里面的甘凭,因此這兩個例子,在數(shù)據(jù)源足夠大的時候火邓,他們的運行的時間丹弱,差別相當?shù)卮罅税〉虑恕R话銇碚f,應(yīng)用到并行流的任何操作都必須是符合縮減操作的三個約束條件躲胳,無狀態(tài)蜓洪,不干預(yù),關(guān)聯(lián)性坯苹!因為這三大約束確保在并行流上執(zhí)行操作的結(jié)果和在順序流上執(zhí)行的結(jié)果是相同的隆檀。

我們在上一篇講縮減操作的時候,提到了三個reduce(),但是我們只講了兩個粹湃,我就不和你們皮了恐仑,直接開講剩下的那一個,在并行流里面为鳄,你們會發(fā)現(xiàn)這個版本的reduce()才是真愛熬账!

public interface Stream<T> extends BaseStream<T, Stream<T>> {
//济赎、鉴逞、、忽略其他無關(guān)緊要的元素
<U> U reduce(U identity,
          BiFunction<U, ? super T, U> accumulator,
          BinaryOperator<U> combiner);
}

在reduce()的這個版本當中司训,accumulator被稱為累加器构捡, combiner被稱為合成器, combiner定義的函數(shù)將accumulator提到的兩個值合并起來壳猜,因此勾徽,我們可以把上面的那個例子改成:

    private static void reduce3th() {
        List<Integer> lists = new ArrayList<>();
        lists.add(1);
        lists.add(2);
        lists.add(3);
        lists.add(4);
        lists.add(5);
        lists.add(6);

        Integer product2 = lists.parallelStream().reduce(1, (a, b) -> a * b,
                                                            (a, b) -> a * b);
        System.out.println("list的積為:" + product2);//720
    }

他們得到的結(jié)果還是一樣的。

你們可能以為accumulator和combiner執(zhí)行的操作是相同的统扳,但其實他們是可以不同的喘帚,下面的例子,你們要認真看了:假設(shè)List里面有三個Integer類型的元素分別為1咒钟,2吹由,3。現(xiàn)在的需求是分別讓List里面的每個元素都放大兩倍后朱嘴,再求積倾鲫。這個需求的正確答案應(yīng)該是48;

    private static void reduce3th() {
        List<Integer> lists = new ArrayList<>();
        lists.add(1);
        lists.add(2);
        lists.add(3);

        Integer product = lists.parallelStream().reduce(1, (a, b) -> a *  (b * 2),
                                                           (a, b) -> a * b);
        System.out.println("product:" + product);//48
    }

累加器部分是將兩個元素分別放大兩倍后,再相乘,合并器萍嬉,是將兩個部分相乘乌昔!如果能理解這里,恭喜你壤追,你的技能有相當大的長進了磕道!估計Stream流你就可以無往而不利了。如果你還不能理解行冰,就應(yīng)該繼續(xù)往下看了溺蕉,跟著我的步伐慢慢走:

    累加器部分(水平向右)
        accumulator
-----------------------------?
thread-1:   1 * 1 * 2   =   2    |    合并器方向(豎直向下)
thread-2:   1 * 2 * 2   =   4    |         combiner
thread-3:   1 * 3 * 2   =   6    |   因此最終的答案是2  *  4  *  6  =   48(沒毛擦尕ぁ)
                                 ˇ
注:水平方向最前面的1就是identity的值

此時,accumulator和combiner執(zhí)行的操作是不是一定不能相同了焙贷。理解這些撵割,對于理解并行流是非常重要的。如果此時的combiner還是和accumulator相同辙芍,那么結(jié)果是什么樣的呢:請看:

    private static void reduce3th() {
        List<Integer> lists = new ArrayList<>();
        lists.add(1);
        lists.add(2);
        lists.add(3);

        Integer product = lists.parallelStream().reduce(1, (a, b) -> a *  (b * 2),
                                                           (a, b) -> a * b * 2 );
        System.out.println("product:" + product);//192
    }

192這個答案是怎么來的啡彬?

    累加器部分(水平向右)
        accumulator
-----------------------------?
thread-1:   1 * 1 * 2   =   2          |    合并器方向(豎直向下)
thread-2:   1 * 2 * 2   =   4  *  2    |         combiner
thread-3:   1 * 3 * 2   =   6  *  2    |   因此最終的答案是2  *  ( 4  *  2 ) *  (6  *  2)  =   192(沒毛病)
                                       ˇ
注:水平方向最前面的1就是identity的值

順序流&并行流&無序流之間的切換操作

對于這三種流的切換故硅,在BaseStream接口中提供了相應(yīng)的方法庶灿,如果你還沒有記住,回頭再看一下第二篇文章吧吃衅。

關(guān)于使用并行流的時候往踢,還有一個點需要記住:如果集合中或者數(shù)組中的元素是有序的徘层,那么對應(yīng)的流也是有序的峻呕。但是在使用并行流時,有時候流是無序的就能獲得性能上的提升趣效。因為如果流是無序的瘦癌,那么流的每個部分都可以被單獨的操作,而不需要與其他部分協(xié)調(diào)跷敬,從而提升性能讯私。(又是無狀態(tài),說好的退休了呢)西傀。所以當流操作的順序不重要的時候斤寇,可以通過BaseStream接口提供的unordered()方法把流轉(zhuǎn)換成一個無序流之后,再進行各種操作拥褂。

另外一點:forEach()方法不一定會保留并行流的順序娘锁,如果在對并行流的每個元素執(zhí)行操作時,也希望保留順序肿仑,那么可以使用forEachOrdered()方法致盟,它的用法和forEach()是一樣的。

因為在發(fā)布第一篇文章的時候尤慰,大家對forEach的反應(yīng)比較大,很多人其實對forEach都有想法:比如調(diào)試難雷蹂,等等伟端。借這個機會,我談一談我對for&forEach的看法:我們在訪問一個數(shù)組元素的時候匪煌,最快的方式肯定是通過索引去訪問的吧责蝠,而for循環(huán)遍歷的時候就是通過下標進行的党巾,所以效率那是相當?shù)母撸钱斘覀兊臄?shù)據(jù)結(jié)構(gòu)不是數(shù)組的時候霜医,比如是鏈表的時候齿拂,可想而知,for循環(huán)的效率是有多低肴敛,但是forEach底層采用的是迭代器的方式署海,他對數(shù)據(jù)結(jié)構(gòu)是沒有要求的,不管上層的數(shù)據(jù)結(jié)構(gòu)是什么医男,他都能保證高效地執(zhí)行砸狞!因此我的最終答案:如果數(shù)據(jù)結(jié)構(gòu)是ArrayList這種數(shù)據(jù)結(jié)構(gòu),那你可以采用for,但是你的數(shù)據(jù)結(jié)構(gòu)如果是LinkList那你千萬別再用for,應(yīng)該果斷采用forEach,因為數(shù)據(jù)一多起來的镀梭,for此時的效率低得可憐刀森,說不定你的機器就癱瘓了。這也是優(yōu)化的一個小技巧吧报账,希望能幫助大家研底。

小結(jié)一下

并行流學會了,你的功力透罢,真的就增長了榜晦。效率再也不是問題了,基本上關(guān)于并行流的方方面面琐凭,這篇文章都已經(jīng)說提到了芽隆,但是Stream在JDK中的變化還是挺快的,我一旦發(fā)現(xiàn)有什么改動统屈,會最快地更新這篇文章胚吁。下一篇我們繼續(xù)探索新知識點。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末愁憔,一起剝皮案震驚了整個濱河市腕扶,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌吨掌,老刑警劉巖半抱,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異膜宋,居然都是意外死亡窿侈,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進店門秋茫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來史简,“玉大人,你說我怎么就攤上這事肛著≡脖” “怎么了跺讯?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵,是天一觀的道長殉农。 經(jīng)常有香客問我刀脏,道長,這世上最難降的妖魔是什么超凳? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任愈污,我火速辦了婚禮,結(jié)果婚禮上聪建,老公的妹妹穿的比我還像新娘钙畔。我一直安慰自己,他們只是感情好金麸,可當我...
    茶點故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布擎析。 她就那樣靜靜地躺著,像睡著了一般挥下。 火紅的嫁衣襯著肌膚如雪揍魂。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天棚瘟,我揣著相機與錄音现斋,去河邊找鬼。 笑死偎蘸,一個胖子當著我的面吹牛庄蹋,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播迷雪,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼限书,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了章咧?” 一聲冷哼從身側(cè)響起倦西,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎赁严,沒想到半個月后扰柠,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡疼约,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年卤档,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片程剥。...
    茶點故事閱讀 38,605評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡裆装,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出倡缠,到底是詐尸還是另有隱情哨免,我是刑警寧澤,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布昙沦,位于F島的核電站琢唾,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏盾饮。R本人自食惡果不足惜采桃,卻給世界環(huán)境...
    茶點故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望丘损。 院中可真熱鬧普办,春花似錦、人聲如沸徘钥。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽呈础。三九已至舆驶,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間而钞,已是汗流浹背沙廉。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留臼节,地道東北人撬陵。 一個月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像网缝,于是被迫代替她去往敵國和親巨税。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,472評論 2 348

推薦閱讀更多精彩內(nèi)容

  • 本文采用實例驅(qū)動的方式途凫,對JAVA8的stream API進行一個深入的介紹垢夹。雖然JAVA8中的stream AP...
    浮梁翁閱讀 25,723評論 3 50
  • Java流庫(java.util.stream) 流提供了一種讓我們可以在比集合更高的概念級別上指定計算的數(shù)據(jù)視圖...
    thorhill閱讀 4,834評論 0 4
  • Int Double Long 設(shè)置特定的stream類型, 提高性能维费,增加特定的函數(shù) 無存儲果元。stream不是一...
    patrick002閱讀 1,267評論 0 0
  • 馬上要年底了,2017年也就只剩下最后一個月了犀盟。為了更好的迎接新的一年而晒,感覺有必要做一個預(yù)測,并且根據(jù)實際情況來不...
    87a17761c1f6閱讀 106評論 0 0
  • Basic introduction https://segmentfault.com/a/11900000042...
    abrocod閱讀 1,041評論 0 0