java迭代的趨勢
更好的并發(fā)
流的引入,lambda,一切都是在為了適應(yīng)現(xiàn)在的硬件架構(gòu): 多核,分布式網(wǎng)絡(luò)架構(gòu)。即期望給用戶提供“輕松”,“安全”的并發(fā)編程接口。 流處理,幾乎免費的并行,用戶在高層次寫邏輯代碼,具體的執(zhí)行由底層的lib來選擇最合適的執(zhí)行方式,比如把計算分布到不同的cpu核上。吸取函數(shù)式語言里高階函數(shù),增加了lambda表達式和函數(shù)Function,增強了java語言的抽象能力,表達能力,“把行為作為參數(shù)傳遞給函數(shù)”,即高階函數(shù)的能力,又稱為“行為參數(shù)化“(與此對應(yīng)的還有”類型參數(shù)化“,即泛型),自此函數(shù)也成為了java語言的一等公民,但是還有方法和類仍然是二等公民,不過提供了一些設(shè)施,將二等公民轉(zhuǎn)化為一等公民。
基于Stream的并發(fā),很少使用synchronized關(guān)鍵字,因為是不同的指導(dǎo)思想,stream的并發(fā)關(guān)注數(shù)據(jù)分塊 而不是 協(xié)調(diào)訪問 , 這樣就像函數(shù)式編程在靠齊,“無共享可變數(shù)據(jù)” + “高階函數(shù)” ,不使用synchronized,來協(xié)調(diào)共享數(shù)據(jù)的訪問(互斥與并發(fā)),而是將數(shù)據(jù)拆分,不共享。

流
流的與集合的一個差異是: 流用于表達計算,集合的元素是計算完之后添加進來或者刪除的,但是流是在固定的數(shù)據(jù)結(jié)構(gòu)上,不能直接刪除和增加,按需計算,定義流的時候計算并不發(fā)生,且只能遍歷一次生成新的流(Java里是這樣,scala里面的流可以多次使用)。
使用流的好處,是能寫出具有如下特點的代碼:
- 聲明式:
- 可復(fù)合:
- 抽象度高
- 免費并行:
流的定義是: 從支持數(shù)據(jù)處理的源生成特定的元素序列。定義決定設(shè)計

如果自己設(shè)計一條流,也應(yīng)該按照這樣的思路來。
所以,流的使用一般就是三件事:
- 定義數(shù)據(jù)源
- 定義中間操作鏈 形成一條流水線
- 終端操作 執(zhí)行流水線(按需計算)生成結(jié)果
無狀態(tài)流和有狀態(tài)流的區(qū)別
有:流內(nèi)部的算子有用戶提供的lambda, 或者 方法引用(方法不是純函數(shù))
無:沒有內(nèi)部狀態(tài),沒有用戶提供的lambda或者方法引用,沒有內(nèi)部可變狀態(tài)。
無狀態(tài)流對并行友好,無縫切換到parallel,而有狀態(tài)的流不行,比如求和,如果用外部變量進行累加,則parallel很容易出錯,但是如果是利用reduce的分開累加,最終將每個累加結(jié)果再累加,就不會有并發(fā)問題。
數(shù)值流存在的原因不是流的復(fù)雜性,而是 基本類型和對應(yīng)的對象類型 之間的裝箱和拆箱性能。
流的生成: 萬物皆可流
萬物都可以作為流的元素,也可以作為流的源頭生成流元素。
- 數(shù)值
- 集合
- 文件
- 空對象
- 函數(shù) (比如無限流,
Stream.iterate(0, n -> n + 2)偶數(shù)的無限流,Stream.iterate(new int[]{0,1}, t-> new int[]{t[1], t[0] + t[1]})斐波那契流,Stream.generate(Math::random)隨機流
流收集:最終計算: 歸約
流水線是lazy的數(shù)據(jù)集的計算迭代器,最終的計算由 terminal action出發(fā),通用的操作即collect,collect接受一個參數(shù)Collector來表示最終的流元素去往何處。
Collectors工具類提供了許多直接的預(yù)定義的歸約器,也提供了一些高階方法生成歸約器,而這一切都離不開背后的基本歸約方法:java.util.stream.Collectors#reducing(U, java.util.function.Function<? super T,? extends U>, java.util.function.BinaryOperator<U>)
U 歸約的初始元素
Function是將流內(nèi)元素轉(zhuǎn)化為待歸約的元素
BinaryOperator是待歸約元素的計算
歸約計算的一個目的,收集,也可由歸約完成。這就涉及到范疇論的理論來,以數(shù)組的收集舉例:
reducing(new List<>(),
(l, e) -> { l.add(e); return l;},
(l1, l2) -> { List l = new List();
l.addAll(l1);
l.addAll(l2);
return l;} }
并且上面的歸約屬于“無狀態(tài)”,可以輕松的用來做 并行。
reducing這個方法之所以能夠作為基本方法是它提供了兩個基本能力:
- 元素到范疇的映射
- 范疇到范疇的映射
代碼實際實現(xiàn)是Collector類,另外Stream提供了一個collect方法,接受三個參數(shù)- supplier, accumulator 和 combiner,來自定義收集,其語義和Collector接口相應(yīng)方法返回的函數(shù)完全相同。
分組: Collector的連接 : groupingBy( , [ toList toSet]), collectAndThen,
復(fù)雜的歸約,可以通過groupingBy以及partitioningBy完成,并且他們之間可以通過多個Collector的連接完成
如:
Map<Type, List<String>> =
collect(groupingBy(Dish::getType,
mapping(Dish:getName, toList())));
或者多級分組
Map<Type, Map<CaloricLevel, List<Dish>>> =
.collect( groupingBy(Dish::getType,
groupingBy( dish -> {
if (dish.getCateGory <= 400 ) return CaloricLevel.DIET;
......
})
));
或者分組統(tǒng)計
Map<Dish.Type, Long> = .collect( groupintBy(Dish::getType, counting() ));
或者分組統(tǒng)計后計算
Map<Dish.Type, Optional<Dish>> = .collect(groupingBy(Dish::getType, maxBy(comparingInt(Dish::getCalories))));
groupingBy(Dish::getType, collectAndThen( maxBy(comparingInt(Dish::getCalories)), Optional::get));
分區(qū) partitioningBy
將流的元素,利用一個謂詞做分類。分區(qū)可以理解為產(chǎn)生了兩個流,并且由 partitioningBy實現(xiàn)的Map實現(xiàn)(特殊map)更高效,緊湊,因為只包含兩個鍵: true 和 false
分區(qū)和分組一樣,可以多級分區(qū),只要 連接 partitioningBy 就可以
收集器性能對比
實現(xiàn)自定義的Collector的目的是為了獲得 比 ’使用內(nèi)置工廠方法創(chuàng)建的收集器‘ 更好的性能,但是也可能讓性能更拉垮,只能說:提供了設(shè)置,讓有能力的同學(xué)可以自由發(fā)揮。
JMH框架來測試。
并行
在Java8之前的并行流,需要主動的拆分數(shù)據(jù),分配給不同的線程,然后還要進行協(xié)調(diào)和同步以避免可能發(fā)生的競爭條件,等到每個線程都完成后,再合并結(jié)果。并且java7引入了一個 “分支/合并”的框架,可以更穩(wěn)定,更不容易出錯的完成這一件事。
不過,相對于java8的并行流還是落后了些,Java8的Stream接口讓用戶免費使用并行,一個指令就可以將順序流轉(zhuǎn)化為并行流(當然,前提是你的數(shù)據(jù)能夠接受并行處理),控制數(shù)據(jù)切分的過程主要是: Spliterator (splitable , iterator)
someStream.parallel() 方法并不會改變實際的流,而是設(shè)置了一個flag,表示 parallel之后的所有操作都是可以并行執(zhí)行的,指需要再調(diào)用 sequential 就可以再變回順序流。
并行流內(nèi)部使用的還是:ForJoinPool,默認的個數(shù)等于cpu的個數(shù)。
java.util.concurrent.ForkJoinPool. common.parallelism 來修改線程池大小, 缺點是jvm級別的,會修改所有并行流的線程池大小,所以一般不修改,如下所示:
System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism","12");
并行流的思想是:數(shù)據(jù)分塊,任務(wù)分塊,然后利用Fork/Joinpool, 實現(xiàn)任務(wù)的計算。
任務(wù)的分塊由Spliterator實現(xiàn)。
Fork/Join Pool框架
思想分治,將任務(wù)拆分,最后合并。
- 可拆分的任務(wù)抽象
ForkJoinTask<T> <= RecursiveTask<T>, 或者 ForkJoinTask<Void> <= RecursiveAction- 任務(wù)執(zhí)行: compute, 任務(wù)在compute內(nèi)部再次拆分,不能拆的計算然后返回結(jié)果,將之前的拆分都合并。
- 執(zhí)行任務(wù)的框架: work stealing。由于任務(wù)拆分的子任務(wù)的計算負載不均衡,導(dǎo)致雖然每個線程的隊列都只有兩個任務(wù),但是其中一個隊列的任務(wù)特別簡單,瞬間完成,而另一個隊列的任務(wù)可能特別耗時,第二個任務(wù)就在等待第一個完成,不能并發(fā)。steal能夠讓空閑/或者負載低的線程偷取忙碌線程的雙端隊列的task來幫助執(zhí)行,這也是為什么 任務(wù) 要分細的原因。
流的拆分: Spliterator
參考:
- https://zhuanlan.zhihu.com/p/504958543
- https://cloud.tencent.com/developer/article/1333605?areaSource=106001.15
interface Spliterator<T> {
boolean tryAdvance(COnsumer< ? super T> action);
Spliterator<T> trySplit();
long estimateSize(); // 不是很準確
int characteristics();
}
trySplit會遞歸調(diào)用,調(diào)用的時候,它會劃分一些元素給它返回的第二個Spliterator,讓他們兩個并行處理。
遞歸的拆分,拆分的過程會影響整個的執(zhí)行效率,在自定義Spliterator的時候,注意效率,采用物理分割,比如將數(shù)據(jù)復(fù)制一份,顯然沒有邏輯分割保存原數(shù)據(jù)引用,修改數(shù)據(jù)范圍來的效率高。另外,拆分的過程收到了 “特性”影響(characteristics方法聲明)

如果當前的Spliterator實例X是可分割的,trySplit()方法會分割X產(chǎn)生一個全新的Spliterator實例Y,原來的X所包含的元素(范圍)也會收縮,類似于X = [a,b,c,d] => X = [a,b], Y = [c,d];如果當前的Spliterator實例X是不可分割的,此方法會返回NULL),具體的分割算法由實現(xiàn)類決定
todo: 對于無限流的并發(fā),是怎么split的?
集合與lambda帶來的高效編程和改變
集合工廠的增強,對于List,Set,map等,SomeCollection.of() 方法會生成 小規(guī)模的高性能的不可變集合類,即集合常量 ,雖然生成的數(shù)據(jù)結(jié)構(gòu)不可變,但是效率更高,就類似于: Arrays.asList() 方法一樣,生成的是視圖。
另外,對于大家常用集合類時候,用到的一些常用操作,都增加了相應(yīng)語義的方法:
-
removeIf: list 和set提供,可以刪除滿足條件的元素,而不會觸發(fā)ConcurrentModificationException,不然就要顯式的使用迭代器和迭代器的刪除方法;
image.png
正確的代碼如下
image.png replaceAll: 替換集合的元素,而不用新生成集合。
remove: 刪除map里面指定的 key和value 對
merge: 把兩個map對元素進行合并的邏輯
computeIfXXXX,compute:高效的計算和填充
ConcurrentHashMap
- set視圖:一個可以時刻同步map的set視圖,xxx.keySet()方法,隨時在set里看到map的變化。
- 基礎(chǔ)類型的歸約:使用對應(yīng)的基礎(chǔ)類型方法會更快,少去了裝箱拆箱的步驟 :
reduceValuesToInt、reduce-KeysToLong
lambda重構(gòu)設(shè)計模式
新的語言特性常常讓現(xiàn)存的編程模式或設(shè)計黯然失色,對設(shè)計經(jīng)驗的總結(jié)陳稱為“設(shè)計模式”。
lambda之所以可以重構(gòu)設(shè)計模式,是因為:將行為參數(shù)化,傳遞給高階函數(shù)。而策略模式,模板模式的核心就是封裝了不同的行為; 觀察者模式,是在發(fā)生一些事件之后,觸發(fā)一些行為的執(zhí)行;責(zé)任鏈模式本質(zhì)是對數(shù)據(jù)多次的操作,完全可以用函數(shù)式編程的組合模式完成;工廠模式,也只是某種特定的函數(shù)罷了:Function<someParameter, SomeClassInstance>;
基于lambda的DSL todo
精讀此章節(jié)內(nèi)容后,最好搭配:
英文版本的: Domain-Specific Languages Martin的書,中文翻譯賊拉垮
DSLs in Action,有引進的話,先看中文看看
還有 antlr4的兩本,因為 martin的書是設(shè)計思想,指導(dǎo)原則,用的工具還是 antlr4 做解析。
Optional,時間 : 非常簡單,略
默認方法和模塊系統(tǒng)
默認方法
模塊系統(tǒng)
模塊系統(tǒng)比較復(fù)雜: 搭配:Nicolai Parlog《 The Java Module System 》
設(shè)計的高層次(軟件架構(gòu)層次)設(shè)計模式:
關(guān)注點分離(separation of concern,SoC)和信息隱藏(information hiding)
- 關(guān)注點分離 推崇的是將:單體的計算機程序分解為一個個相互獨立的特性
采用關(guān)注點分離,可以將軟件的功能,作用等劃分到名為“模塊“的獨立組成部分中去,所以,模塊是具有“內(nèi)聚”特質(zhì)的一組代碼,它與其他模塊的代碼很少耦合;通過模塊組織類,可以清晰地描繪出應(yīng)用程序類與類之間的可見性關(guān)系。
Java的包機制并為從本質(zhì)上支持模塊化,它的粒度太粗。而模塊化的粒度更細,且控制檢查是編譯期的。帶來的好處就是:可以使得各項工作獨立開展,減少組件之間的依賴,便于團隊合作,有利于推動組建重用,系統(tǒng)整體的維護性更好。
- 信息隱藏: 隱藏信息能夠減少局部變更對其他部分程序的影響,從而避免“變更傳遞”。
在低層次(代碼層次) 的表現(xiàn)就是封裝。雖然我們具有private,protected,public 關(guān)鍵字,但是就語言層面而言,Java 9 出現(xiàn)之前, 編譯器無法依據(jù)語言結(jié)構(gòu)判斷某個類或者包僅供某個特定目標訪問。
Java9之前的Java內(nèi)置模塊化的問題:
- 有限的可見性控制:三個描述符只能控制包級別的類訪問,無法描述 包之間的訪問。比如:“希望一個包中的某個類或接口可以被另外一個包中的類或接口訪問,那么只能將它聲明為 public。這樣一來,任何人都可以訪問這些類和接口了”。這樣就可能讓代碼被隨意使用,給開發(fā)者演進自己的代碼帶來困難。
- 類路徑的問題:Java編譯器把所有的類都打入一個扁平的jar包中,并且把jar包放到class path上,jvm可以動態(tài)一句類的路徑從中定位并且加載相關(guān)的類。然后,這樣存在了幾個嚴重的問題:
- 無法通過路徑指定版本。比如如果類路徑上存在同一個庫的兩個版本,會發(fā)生什么。而大型項目中很常見,它的不同組件使用同一個庫的不同版本:解決方法有
- 使用自定義ClassLoader來隔離: http://www.blogjava.net/landon/category/54860.html(值得注意的兩點是:公有接口可以由系統(tǒng)類加載器加載,舊的類的實例和class很難被卸載)。比如: elasticsearch中的插件加載機制,實現(xiàn)了自定義的classLoader 。 甚至可以用ClassLoader來實現(xiàn)熱部署:https://cloud.tencent.com/developer/article/1915650
- 或者是OSGI
- sofa-ark是動態(tài)熱部署和類隔離框架,支付寶開源
-
類路徑不支持顯式的依賴:
image.png
Java9提供了一個新的單位: 模塊。通過 module聲明,緊接著的是模塊的名字和主體的內(nèi)容,定義在特殊的文件:module-info.class, 下圖可知,module的層級是高于package的。


使用命令可以指導(dǎo)哪些目錄和類文件會被打包進入生成的JAR文件中:
javac module-info.java xxx/yyyy/zzz.java -d target
jar cvfe xxxxx.jar xxx.yyy.zzz -C target
然后運行執(zhí)行命令:
java --module-path xxxxx.jar --module moduleName.xxx.yyy.zzz

并發(fā)性
無論是基于 Future 的異步 API 還是反應(yīng)式異步 API,被調(diào)方法的概念體(conceptual body) 都在另一個線程中執(zhí)行,調(diào)用方很可能已經(jīng)退出了執(zhí)行,不在調(diào)用異常處理器的作用域內(nèi)。很明顯,這種非常規(guī)行為觸發(fā)的異常需要通過其他的動作來處理。
CompletableFuture
和Scala的Future很類似
反應(yīng)式編程
直接看《反應(yīng)式設(shè)計模式》就好了
函數(shù)式
個人了解較多,忽略。


