Java8之Stream流(四)并行流

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

隨著對流API認識的慢慢深入,本章我們要討論的知識點是流API里面的并行流了。在開始討論并行流之前,我先引發一下大家的思考,就你看到這篇文章的時間,你們是不是經常聽到,Intel i7 CPU什么8核16線程,什么Android手機8核4GB這種消息,既然我們是處于一個多核處理器的時代,你們有沒有想過并行地操作數組和集合框架,從而高速地執行我們對數組或者集合的一些操作呢?或許你有想過這個問題,但是因為并行編程比較復雜,所以這些想法還停留在你的腦海當中,又或者你已經在路上了,反正你們就是最棒的(我他媽都這么夸你們了,就不能點個喜歡?),不管如何,在你看到這一篇文章的時候,我將帶你走向并行地操作數組或者集合,當然是使用我們的并行流知識啦。

并行流

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

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
    }
}

得到結果和上一篇文章的一模一樣。但是因為乘法和加法操作是可以發生在不同的線程里面的,因此這兩個例子,在數據源足夠大的時候,他們的運行的時間,差別相當地大了啊。一般來說,應用到并行流的任何操作都必須是符合縮減操作的三個約束條件,無狀態,不干預,關聯性!因為這三大約束確保在并行流上執行操作的結果和在順序流上執行的結果是相同的。

我們在上一篇講縮減操作的時候,提到了三個reduce(),但是我們只講了兩個,我就不和你們皮了,直接開講剩下的那一個,在并行流里面,你們會發現這個版本的reduce()才是真愛啊!

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

在reduce()的這個版本當中,accumulator被稱為累加器, combiner被稱為合成器, combiner定義的函數將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
    }

他們得到的結果還是一樣的。

你們可能以為accumulator和combiner執行的操作是相同的,但其實他們是可以不同的,下面的例子,你們要認真看了:假設List里面有三個Integer類型的元素分別為1,2,3。現在的需求是分別讓List里面的每個元素都放大兩倍后,再求積。這個需求的正確答案應該是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流你就可以無往而不利了。如果你還不能理解,就應該繼續往下看了,跟著我的步伐慢慢走:

    累加器部分(水平向右)
        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執行的操作是不是一定不能相同了。理解這些,對于理解并行流是非常重要的。如果此時的combiner還是和accumulator相同,那么結果是什么樣的呢:請看:

    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接口中提供了相應的方法,如果你還沒有記住,回頭再看一下第二篇文章吧。

關于使用并行流的時候,還有一個點需要記住:如果集合中或者數組中的元素是有序的,那么對應的流也是有序的。但是在使用并行流時,有時候流是無序的就能獲得性能上的提升。因為如果流是無序的,那么流的每個部分都可以被單獨的操作,而不需要與其他部分協調,從而提升性能。(又是無狀態,說好的退休了呢)。所以當流操作的順序不重要的時候,可以通過BaseStream接口提供的unordered()方法把流轉換成一個無序流之后,再進行各種操作。

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

因為在發布第一篇文章的時候,大家對forEach的反應比較大,很多人其實對forEach都有想法:比如調試難,等等。借這個機會,我談一談我對for&forEach的看法:我們在訪問一個數組元素的時候,最快的方式肯定是通過索引去訪問的吧,而for循環遍歷的時候就是通過下標進行的,所以效率那是相當的高,但是當我們的數據結構不是數組的時候,比如是鏈表的時候,可想而知,for循環的效率是有多低,但是forEach底層采用的是迭代器的方式,他對數據結構是沒有要求的,不管上層的數據結構是什么,他都能保證高效地執行!因此我的最終答案:如果數據結構是ArrayList這種數據結構,那你可以采用for,但是你的數據結構如果是LinkList那你千萬別再用for,應該果斷采用forEach,因為數據一多起來的,for此時的效率低得可憐,說不定你的機器就癱瘓了。這也是優化的一個小技巧吧,希望能幫助大家。

小結一下

并行流學會了,你的功力,真的就增長了。效率再也不是問題了,基本上關于并行流的方方面面,這篇文章都已經說提到了,但是Stream在JDK中的變化還是挺快的,我一旦發現有什么改動,會最快地更新這篇文章。下一篇我們繼續探索新知識點。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,836評論 6 540
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,275評論 3 428
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,904評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,633評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,368評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,736評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,740評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,919評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,481評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,235評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,427評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,968評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,656評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,055評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,348評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,160評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,380評論 2 379

推薦閱讀更多精彩內容

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