謹(jǐn)慎使用 java8 并行流


問(wèn)題引出
最近接觸到一個(gè)關(guān)于排序的問(wèn)題:模擬奧運(yùn)會(huì)統(tǒng)計(jì)對(duì)每個(gè)國(guó)家獲取的獎(jiǎng)牌數(shù)進(jìn)行排序,排序規(guī)則:排序順序依次為金牌炸庞,銀牌呕乎,銅牌的數(shù)量,若金牌的數(shù)量相等則比較銀牌惩猫,銀牌的數(shù)量相等芝硬,則比較銅牌,若三種獎(jiǎng)牌數(shù)量都一樣帆锋,則根據(jù)國(guó)家的英文字符排序吵取,最終輸出排序后的國(guó)家名稱:
? ? ? ??其實(shí)也不是一個(gè)很復(fù)雜的問(wèn)題禽额;第一時(shí)間想到j(luò)ava8提供的流操作锯厢,直使用sorted方法直接排序,可以同時(shí)支持多個(gè)字段的排序脯倒。代碼如下:

public static void  sortedCountry(List<CountryInfo> countryInfos) {
        countryInfos.parallelStream().sorted(Comparator
                .comparingInt(CountryInfo::getGold)
                .thenComparingInt(CountryInfo::getSilver)
                .thenComparingInt(CountryInfo::getBronze)
                .thenComparing((a, b) -> b.getCountry().compareTo(a.getCountry()))
                .reversed())
               .forEach(e -> System.out.print(e.getCountry() + ", "));
    }


    private static class CountryInfo {
        private String country;
        private int gold;
        private int silver;
        private int bronze;

        public CountryInfo(String country, int gold, int silver, int bronze) {
            this.country = country;
            this.gold = gold;
            this.silver = silver;
            this.bronze = bronze;
        }

        public String getCountry() {
            return country;
        }

        public int getGold() {
            return gold;
        }

        public int getSilver() {
            return silver;
        }

        public int getBronze() {
            return bronze;
        }
    }

代碼中sortedCountry是核心方法实辑,分析該段代碼的話,看起來(lái)并沒(méi)有什么問(wèn)題藻丢。但是運(yùn)行的時(shí)候出問(wèn)題了剪撬,如下:


測(cè)試結(jié)果

預(yù)期結(jié)果應(yīng)該是china,japan悠反,uk残黑,aust,us斋否。
最后發(fā)現(xiàn)梨水,代碼改成這樣就行了。

public static void  sortedCountry(List<CountryInfo> countryInfos) {
        List<CountryInfo> collect = countryInfos.parallelStream().sorted(Comparator
                .comparingInt(CountryInfo::getGold)
                .thenComparingInt(CountryInfo::getSilver)
                .thenComparingInt(CountryInfo::getBronze)
                .thenComparing((a, b) -> b.getCountry().compareTo(a.getCountry()))
                .reversed())
                .collect(Collectors.toList());
        for (CountryInfo countryInfo : collect) {
            System.out.print(countryInfo.getCountry() + ", ");
        }
    }

運(yùn)行結(jié)果如下:


運(yùn)行結(jié)果

兩段代碼對(duì)比一下茵臭,可以發(fā)現(xiàn)疫诽,唯一不一樣的地方在于對(duì)最終集合遍歷操作,第一段代碼在流中直接執(zhí)行遍歷旦委,第二段代碼使用for循環(huán)奇徒,所以問(wèn)題應(yīng)該在并行流操作時(shí)出現(xiàn)的問(wèn)題。parallelStream工作原理采用forkjoin的思想缨硝,也就是采用jdk內(nèi)部的forkjoin框架摩钙,等于開(kāi)啟多個(gè)線程對(duì)集合操作,第二段代碼運(yùn)行時(shí)查辩,由于使用到流操作的執(zhí)行結(jié)果胖笛,即collect對(duì)象奕短,在進(jìn)行for循環(huán)時(shí)候會(huì)用到,會(huì)一直阻塞匀钧,直到流被執(zhí)行完成翎碑,才會(huì)開(kāi)始遍歷,所以結(jié)果是正確的之斯。


分析原因
為什么會(huì)產(chǎn)生這樣的結(jié)果:《java8實(shí)戰(zhàn)》中這樣描述:

流操作:無(wú)狀態(tài)和有狀態(tài)
你已經(jīng)看到了很多的流操作日杈。乍一看流操作簡(jiǎn)直是靈丹妙藥,而且只要在從集合生成流的
時(shí)候把Stream換成parallelStream就可以實(shí)現(xiàn)并行佑刷。
當(dāng)然莉擒,對(duì)于許多應(yīng)用來(lái)說(shuō)確實(shí)是這樣,就像前面的那些例子瘫絮。你可以把一張菜單變成流涨冀,
用filter選出某一類的菜肴,然后對(duì)得到的流做map來(lái)對(duì)卡路里求和麦萤,最后reduce得到菜單
的總熱量鹿鳖。這個(gè)流計(jì)算甚至可以并行進(jìn)行。但這些操作的特性并不相同壮莹。它們需要操作的內(nèi)部
狀態(tài)還是有些問(wèn)題的翅帜。
諸如map或filter等操作會(huì)從輸入流中獲取每一個(gè)元素,并在輸出流中得到0或1個(gè)結(jié)果命满。
這些操作一般都是無(wú)狀態(tài)的:它們沒(méi)有內(nèi)部狀態(tài)(假設(shè)用戶提供的Lambda或方法引用沒(méi)有內(nèi)
部可變狀態(tài))涝滴。
但諸如reduce、sum胶台、max等操作需要內(nèi)部狀態(tài)來(lái)累積結(jié)果歼疮。在上面的情況下,內(nèi)部狀態(tài)
很小诈唬。在我們的例子里就是一個(gè)int或double韩脏。不管流中有多少元素要處理,內(nèi)部狀態(tài)都是有界的讯榕。
相反骤素,諸如sort或distinct等操作一開(kāi)始都和filter和map差不多——都是接受一個(gè)
流,再生成一個(gè)流(中間操作)愚屁,但有一個(gè)關(guān)鍵的區(qū)別济竹。從流中排序和刪除重復(fù)項(xiàng)時(shí)都需要知
道先前的歷史。例如霎槐,排序要求所有元素都放入緩沖區(qū)后才能給輸出流加入一個(gè)項(xiàng)目送浊,這一操
作的存儲(chǔ)要求是無(wú)界的。要是流比較大或是無(wú)限的丘跌,就可能會(huì)有問(wèn)題(把質(zhì)數(shù)流倒序會(huì)做什么
呢袭景?它應(yīng)當(dāng)返回最大的質(zhì)數(shù)唁桩,但數(shù)學(xué)告訴我們它不存在)。我們把這些操作叫作有狀態(tài)操作耸棒。

所以第一段代碼輸出錯(cuò)誤結(jié)果的原因是foreach操作讀取了半成品的結(jié)果荒澡。想要得到正確的結(jié)果,可以使用第二段代碼与殃,也可以parallelStream改為stream单山,讓流串行執(zhí)行也是可以的,如下:

    public static void  sortedCountry(List<CountryInfo> countryInfos) {
        countryInfos.stream().sorted(Comparator
                .comparingInt(CountryInfo::getGold)
                .thenComparingInt(CountryInfo::getSilver)
                .thenComparingInt(CountryInfo::getBronze)
                .thenComparing((a, b) -> b.getCountry().compareTo(a.getCountry()))
                .reversed())
                .forEach(e -> System.out.print(e.getCountry() + ", "));

    }
串行流執(zhí)行結(jié)果

總結(jié)(個(gè)人理解)
書(shū)中對(duì)有狀態(tài)和無(wú)狀態(tài)流描述的比較抽象幅疼,其實(shí)可以結(jié)合當(dāng)前的例子來(lái)理解:有狀態(tài)的流米奸,前邊使用的sort操作,它排序元素的時(shí)候比如說(shuō)A爽篷,B悴晰,C,最壞的情況比較三次逐工,即A和B比較铡溪,B和C比較,A和C比較钻弄,可以得到最終的排序結(jié)果佃却;無(wú)狀態(tài)的流者吁,比如說(shuō)sum窘俺,如果要求三個(gè)元素的和,A复凳,B瘤泪,C,需要兩次相加就可以得到最終結(jié)果育八。其他操作也可以采用這種方式分析对途。
下圖是《java8實(shí)戰(zhàn)》的總結(jié):

中間操作和終端操作

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市髓棋,隨后出現(xiàn)的幾起案子实檀,更是在濱河造成了極大的恐慌,老刑警劉巖按声,帶你破解...
    沈念sama閱讀 221,548評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件膳犹,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡签则,警方通過(guò)查閱死者的電腦和手機(jī)须床,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)渐裂,“玉大人豺旬,你說(shuō)我怎么就攤上這事钠惩。” “怎么了族阅?”我有些...
    開(kāi)封第一講書(shū)人閱讀 167,990評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵篓跛,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我坦刀,道長(zhǎng)举塔,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,618評(píng)論 1 296
  • 正文 為了忘掉前任求泰,我火速辦了婚禮央渣,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘渴频。我一直安慰自己芽丹,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,618評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布卜朗。 她就那樣靜靜地躺著拔第,像睡著了一般。 火紅的嫁衣襯著肌膚如雪场钉。 梳的紋絲不亂的頭發(fā)上蚊俺,一...
    開(kāi)封第一講書(shū)人閱讀 52,246評(píng)論 1 308
  • 那天,我揣著相機(jī)與錄音逛万,去河邊找鬼泳猬。 笑死,一個(gè)胖子當(dāng)著我的面吹牛宇植,可吹牛的內(nèi)容都是我干的得封。 我是一名探鬼主播,決...
    沈念sama閱讀 40,819評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼指郁,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼忙上!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起闲坎,我...
    開(kāi)封第一講書(shū)人閱讀 39,725評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤疫粥,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后腰懂,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體梗逮,經(jīng)...
    沈念sama閱讀 46,268評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,356評(píng)論 3 340
  • 正文 我和宋清朗相戀三年悯恍,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了库糠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,488評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖瞬欧,靈堂內(nèi)的尸體忽然破棺而出贷屎,到底是詐尸還是另有隱情,我是刑警寧澤艘虎,帶...
    沈念sama閱讀 36,181評(píng)論 5 350
  • 正文 年R本政府宣布唉侄,位于F島的核電站,受9級(jí)特大地震影響野建,放射性物質(zhì)發(fā)生泄漏属划。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,862評(píng)論 3 333
  • 文/蒙蒙 一候生、第九天 我趴在偏房一處隱蔽的房頂上張望同眯。 院中可真熱鬧,春花似錦唯鸭、人聲如沸须蜗。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,331評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)明肮。三九已至,卻和暖如春缭付,著一層夾襖步出監(jiān)牢的瞬間柿估,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,445評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工陷猫, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留秫舌,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,897評(píng)論 3 376
  • 正文 我出身青樓烙丛,卻偏偏與公主長(zhǎng)得像舅巷,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子河咽,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,500評(píng)論 2 359