java迭代的趨勢(shì)
更好的并發(fā)
流的引入,lambda,一切都是在為了適應(yīng)現(xiàn)在的硬件架構(gòu): 多核,分布式網(wǎng)絡(luò)架構(gòu)。即期望給用戶(hù)提供“輕松”,“安全”的并發(fā)編程接口。 流處理,幾乎免費(fèi)的并行,用戶(hù)在高層次寫(xiě)邏輯代碼,具體的執(zhí)行由底層的lib來(lái)選擇最合適的執(zhí)行方式,比如把計(jì)算分布到不同的cpu核上。吸取函數(shù)式語(yǔ)言里高階函數(shù),增加了lambda表達(dá)式和函數(shù)Function,增強(qiáng)了java語(yǔ)言的抽象能力,表達(dá)能力,“把行為作為參數(shù)傳遞給函數(shù)”,即高階函數(shù)的能力,又稱(chēng)為“行為參數(shù)化“(與此對(duì)應(yīng)的還有”類(lèi)型參數(shù)化“,即泛型),自此函數(shù)也成為了java語(yǔ)言的一等公民,但是還有方法和類(lèi)仍然是二等公民,不過(guò)提供了一些設(shè)施,將二等公民轉(zhuǎn)化為一等公民。
基于Stream的并發(fā),很少使用synchronized關(guān)鍵字,因?yàn)槭遣煌闹笇?dǎo)思想,stream的并發(fā)關(guān)注數(shù)據(jù)分塊 而不是 協(xié)調(diào)訪問(wèn) , 這樣就像函數(shù)式編程在靠齊,“無(wú)共享可變數(shù)據(jù)” + “高階函數(shù)” ,不使用synchronized,來(lái)協(xié)調(diào)共享數(shù)據(jù)的訪問(wèn)(互斥與并發(fā)),而是將數(shù)據(jù)拆分,不共享。
流
流的與集合的一個(gè)差異是: 流用于表達(dá)計(jì)算,集合的元素是計(jì)算完之后添加進(jìn)來(lái)或者刪除的,但是流是在固定的數(shù)據(jù)結(jié)構(gòu)上,不能直接刪除和增加,按需計(jì)算,定義流的時(shí)候計(jì)算并不發(fā)生,且只能遍歷一次生成新的流(Java里是這樣,scala里面的流可以多次使用)。
使用流的好處,是能寫(xiě)出具有如下特點(diǎn)的代碼:
- 聲明式:
- 可復(fù)合:
- 抽象度高
- 免費(fèi)并行:
流的定義是: 從支持?jǐn)?shù)據(jù)處理的源生成特定的元素序列。定義決定設(shè)計(jì)
如果自己設(shè)計(jì)一條流,也應(yīng)該按照這樣的思路來(lái)。
所以,流的使用一般就是三件事:
- 定義數(shù)據(jù)源
- 定義中間操作鏈 形成一條流水線
- 終端操作 執(zhí)行流水線(按需計(jì)算)生成結(jié)果
無(wú)狀態(tài)流和有狀態(tài)流的區(qū)別
有:流內(nèi)部的算子有用戶(hù)提供的lambda, 或者 方法引用(方法不是純函數(shù))
無(wú):沒(méi)有內(nèi)部狀態(tài),沒(méi)有用戶(hù)提供的lambda或者方法引用,沒(méi)有內(nèi)部可變狀態(tài)。
無(wú)狀態(tài)流對(duì)并行友好,無(wú)縫切換到parallel,而有狀態(tài)的流不行,比如求和,如果用外部變量進(jìn)行累加,則parallel很容易出錯(cuò),但是如果是利用reduce的分開(kāi)累加,最終將每個(gè)累加結(jié)果再累加,就不會(huì)有并發(fā)問(wèn)題。
數(shù)值流存在的原因不是流的復(fù)雜性,而是 基本類(lèi)型和對(duì)應(yīng)的對(duì)象類(lèi)型 之間的裝箱和拆箱性能。
流的生成: 萬(wàn)物皆可流
萬(wàn)物都可以作為流的元素,也可以作為流的源頭生成流元素。
- 數(shù)值
- 集合
- 文件
- 空對(duì)象
- 函數(shù) (比如無(wú)限流,
Stream.iterate(0, n -> n + 2)
偶數(shù)的無(wú)限流,Stream.iterate(new int[]{0,1}, t-> new int[]{t[1], t[0] + t[1]})
斐波那契流,Stream.generate(Math::random)
隨機(jī)流
流收集:最終計(jì)算: 歸約
流水線是lazy的數(shù)據(jù)集的計(jì)算迭代器,最終的計(jì)算由 terminal action出發(fā),通用的操作即collect,collect接受一個(gè)參數(shù)Collector來(lái)表示最終的流元素去往何處。
Collectors工具類(lèi)提供了許多直接的預(yù)定義的歸約器,也提供了一些高階方法生成歸約器,而這一切都離不開(kāi)背后的基本歸約方法: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是待歸約元素的計(jì)算
歸約計(jì)算的一個(gè)目的,收集,也可由歸約完成。這就涉及到范疇論的理論來(lái),以數(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;} }
并且上面的歸約屬于“無(wú)狀態(tài)”,可以輕松的用來(lái)做 并行。
reducing這個(gè)方法之所以能夠作為基本方法是它提供了兩個(gè)基本能力:
- 元素到范疇的映射
- 范疇到范疇的映射
代碼實(shí)際實(shí)現(xiàn)是Collector類(lèi),另外Stream提供了一個(gè)collect方法,接受三個(gè)參數(shù)- supplier, accumulator 和 combiner,來(lái)自定義收集,其語(yǔ)義和Collector接口相應(yīng)方法返回的函數(shù)完全相同。
分組: Collector的連接 : groupingBy( , [ toList toSet]), collectAndThen,
復(fù)雜的歸約,可以通過(guò)groupingBy以及partitioningBy完成,并且他們之間可以通過(guò)多個(gè)Collector的連接完成
如:
Map<Type, List<String>> =
collect(groupingBy(Dish::getType,
mapping(Dish:getName, toList())));
或者多級(jí)分組
Map<Type, Map<CaloricLevel, List<Dish>>> =
.collect( groupingBy(Dish::getType,
groupingBy( dish -> {
if (dish.getCateGory <= 400 ) return CaloricLevel.DIET;
......
})
));
或者分組統(tǒng)計(jì)
Map<Dish.Type, Long> = .collect( groupintBy(Dish::getType, counting() ));
或者分組統(tǒng)計(jì)后計(jì)算
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
將流的元素,利用一個(gè)謂詞做分類(lèi)。分區(qū)可以理解為產(chǎn)生了兩個(gè)流,并且由 partitioningBy實(shí)現(xiàn)的Map實(shí)現(xiàn)(特殊map)更高效,緊湊,因?yàn)橹话瑑蓚€(gè)鍵: true 和 false
分區(qū)和分組一樣,可以多級(jí)分區(qū),只要 連接 partitioningBy 就可以
收集器性能對(duì)比
實(shí)現(xiàn)自定義的Collector的目的是為了獲得 比 ’使用內(nèi)置工廠方法創(chuàng)建的收集器‘ 更好的性能,但是也可能讓性能更拉垮,只能說(shuō):提供了設(shè)置,讓有能力的同學(xué)可以自由發(fā)揮。
JMH框架來(lái)測(cè)試。
并行
在Java8之前的并行流,需要主動(dòng)的拆分?jǐn)?shù)據(jù),分配給不同的線程,然后還要進(jìn)行協(xié)調(diào)和同步以避免可能發(fā)生的競(jìng)爭(zhēng)條件,等到每個(gè)線程都完成后,再合并結(jié)果。并且java7引入了一個(gè) “分支/合并”的框架,可以更穩(wěn)定,更不容易出錯(cuò)的完成這一件事。
不過(guò),相對(duì)于java8的并行流還是落后了些,Java8的Stream接口讓用戶(hù)免費(fèi)使用并行,一個(gè)指令就可以將順序流轉(zhuǎn)化為并行流(當(dāng)然,前提是你的數(shù)據(jù)能夠接受并行處理),控制數(shù)據(jù)切分的過(guò)程主要是: Spliterator (splitable , iterator)
someStream.parallel() 方法并不會(huì)改變實(shí)際的流,而是設(shè)置了一個(gè)flag,表示 parallel之后的所有操作都是可以并行執(zhí)行的,指需要再調(diào)用 sequential 就可以再變回順序流。
并行流內(nèi)部使用的還是:ForJoinPool,默認(rèn)的個(gè)數(shù)等于cpu的個(gè)數(shù)。
java.util.concurrent.ForkJoinPool. common.parallelism 來(lái)修改線程池大小, 缺點(diǎn)是jvm級(jí)別的,會(huì)修改所有并行流的線程池大小,所以一般不修改,如下所示:
System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism","12");
并行流的思想是:數(shù)據(jù)分塊,任務(wù)分塊,然后利用Fork/Joinpool, 實(shí)現(xiàn)任務(wù)的計(jì)算。
任務(wù)的分塊由Spliterator實(shí)現(xiàn)。
Fork/Join Pool框架
思想分治,將任務(wù)拆分,最后合并。
- 可拆分的任務(wù)抽象
ForkJoinTask<T> <= RecursiveTask<T>, 或者 ForkJoinTask<Void> <= RecursiveAction
- 任務(wù)執(zhí)行: compute, 任務(wù)在compute內(nèi)部再次拆分,不能拆的計(jì)算然后返回結(jié)果,將之前的拆分都合并。
- 執(zhí)行任務(wù)的框架: work stealing。由于任務(wù)拆分的子任務(wù)的計(jì)算負(fù)載不均衡,導(dǎo)致雖然每個(gè)線程的隊(duì)列都只有兩個(gè)任務(wù),但是其中一個(gè)隊(duì)列的任務(wù)特別簡(jiǎn)單,瞬間完成,而另一個(gè)隊(duì)列的任務(wù)可能特別耗時(shí),第二個(gè)任務(wù)就在等待第一個(gè)完成,不能并發(fā)。steal能夠讓空閑/或者負(fù)載低的線程偷取忙碌線程的雙端隊(duì)列的task來(lái)幫助執(zhí)行,這也是為什么 任務(wù) 要分細(xì)的原因。
流的拆分: 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(); // 不是很準(zhǔn)確
int characteristics();
}
trySplit會(huì)遞歸調(diào)用,調(diào)用的時(shí)候,它會(huì)劃分一些元素給它返回的第二個(gè)Spliterator,讓他們兩個(gè)并行處理。
遞歸的拆分,拆分的過(guò)程會(huì)影響整個(gè)的執(zhí)行效率,在自定義Spliterator的時(shí)候,注意效率,采用物理分割,比如將數(shù)據(jù)復(fù)制一份,顯然沒(méi)有邏輯分割保存原數(shù)據(jù)引用,修改數(shù)據(jù)范圍來(lái)的效率高。另外,拆分的過(guò)程收到了 “特性”影響(characteristics方法聲明)
如果當(dāng)前的Spliterator實(shí)例X是可分割的,trySplit()方法會(huì)分割X產(chǎn)生一個(gè)全新的Spliterator實(shí)例Y,原來(lái)的X所包含的元素(范圍)也會(huì)收縮,類(lèi)似于X = [a,b,c,d] => X = [a,b], Y = [c,d];如果當(dāng)前的Spliterator實(shí)例X是不可分割的,此方法會(huì)返回NULL),具體的分割算法由實(shí)現(xiàn)類(lèi)決定
todo: 對(duì)于無(wú)限流的并發(fā),是怎么split的?
集合與lambda帶來(lái)的高效編程和改變
集合工廠的增強(qiáng),對(duì)于List,Set,map等,SomeCollection.of() 方法會(huì)生成 小規(guī)模的高性能的不可變集合類(lèi),即集合常量 ,雖然生成的數(shù)據(jù)結(jié)構(gòu)不可變,但是效率更高,就類(lèi)似于: Arrays.asList() 方法一樣,生成的是視圖。
另外,對(duì)于大家常用集合類(lèi)時(shí)候,用到的一些常用操作,都增加了相應(yīng)語(yǔ)義的方法:
-
removeIf: list 和set提供,可以刪除滿(mǎn)足條件的元素,而不會(huì)觸發(fā)ConcurrentModificationException,不然就要顯式的使用迭代器和迭代器的刪除方法;
image.png
正確的代碼如下
image.png replaceAll: 替換集合的元素,而不用新生成集合。
remove: 刪除map里面指定的 key和value 對(duì)
merge: 把兩個(gè)map對(duì)元素進(jìn)行合并的邏輯
computeIfXXXX,compute:高效的計(jì)算和填充
ConcurrentHashMap
- set視圖:一個(gè)可以時(shí)刻同步map的set視圖,xxx.keySet()方法,隨時(shí)在set里看到map的變化。
- 基礎(chǔ)類(lèi)型的歸約:使用對(duì)應(yīng)的基礎(chǔ)類(lèi)型方法會(huì)更快,少去了裝箱拆箱的步驟 :
reduceValuesToInt、reduce-KeysToLong
lambda重構(gòu)設(shè)計(jì)模式
新的語(yǔ)言特性常常讓現(xiàn)存的編程模式或設(shè)計(jì)黯然失色,對(duì)設(shè)計(jì)經(jīng)驗(yàn)的總結(jié)陳稱(chēng)為“設(shè)計(jì)模式”。
lambda之所以可以重構(gòu)設(shè)計(jì)模式,是因?yàn)椋簩⑿袨閰?shù)化,傳遞給高階函數(shù)。而策略模式,模板模式的核心就是封裝了不同的行為; 觀察者模式,是在發(fā)生一些事件之后,觸發(fā)一些行為的執(zhí)行;責(zé)任鏈模式本質(zhì)是對(duì)數(shù)據(jù)多次的操作,完全可以用函數(shù)式編程的組合模式完成;工廠模式,也只是某種特定的函數(shù)罷了:Function<someParameter, SomeClassInstance>;
基于lambda的DSL todo
精讀此章節(jié)內(nèi)容后,最好搭配:
英文版本的: Domain-Specific Languages Martin的書(shū),中文翻譯賊拉垮
DSLs in Action,有引進(jìn)的話,先看中文看看
還有 antlr4的兩本,因?yàn)?martin的書(shū)是設(shè)計(jì)思想,指導(dǎo)原則,用的工具還是 antlr4 做解析。
Optional,時(shí)間 : 非常簡(jiǎn)單,略
默認(rèn)方法和模塊系統(tǒng)
默認(rèn)方法
模塊系統(tǒng)
模塊系統(tǒng)比較復(fù)雜: 搭配:Nicolai Parlog《 The Java Module System 》
設(shè)計(jì)的高層次(軟件架構(gòu)層次)設(shè)計(jì)模式:
關(guān)注點(diǎn)分離(separation of concern,SoC)和信息隱藏(information hiding)
- 關(guān)注點(diǎn)分離 推崇的是將:?jiǎn)误w的計(jì)算機(jī)程序分解為一個(gè)個(gè)相互獨(dú)立的特性
采用關(guān)注點(diǎn)分離,可以將軟件的功能,作用等劃分到名為“模塊“的獨(dú)立組成部分中去,所以,模塊是具有“內(nèi)聚”特質(zhì)的一組代碼,它與其他模塊的代碼很少耦合;通過(guò)模塊組織類(lèi),可以清晰地描繪出應(yīng)用程序類(lèi)與類(lèi)之間的可見(jiàn)性關(guān)系。
Java的包機(jī)制并為從本質(zhì)上支持模塊化,它的粒度太粗。而模塊化的粒度更細(xì),且控制檢查是編譯期的。帶來(lái)的好處就是:可以使得各項(xiàng)工作獨(dú)立開(kāi)展,減少組件之間的依賴(lài),便于團(tuán)隊(duì)合作,有利于推動(dòng)組建重用,系統(tǒng)整體的維護(hù)性更好。
- 信息隱藏: 隱藏信息能夠減少局部變更對(duì)其他部分程序的影響,從而避免“變更傳遞”。
在低層次(代碼層次) 的表現(xiàn)就是封裝。雖然我們具有private,protected,public 關(guān)鍵字,但是就語(yǔ)言層面而言,Java 9 出現(xiàn)之前, 編譯器無(wú)法依據(jù)語(yǔ)言結(jié)構(gòu)判斷某個(gè)類(lèi)或者包僅供某個(gè)特定目標(biāo)訪問(wèn)。
Java9之前的Java內(nèi)置模塊化的問(wèn)題:
- 有限的可見(jiàn)性控制:三個(gè)描述符只能控制包級(jí)別的類(lèi)訪問(wèn),無(wú)法描述 包之間的訪問(wèn)。比如:“希望一個(gè)包中的某個(gè)類(lèi)或接口可以被另外一個(gè)包中的類(lèi)或接口訪問(wèn),那么只能將它聲明為 public。這樣一來(lái),任何人都可以訪問(wèn)這些類(lèi)和接口了”。這樣就可能讓代碼被隨意使用,給開(kāi)發(fā)者演進(jìn)自己的代碼帶來(lái)困難。
- 類(lèi)路徑的問(wèn)題:Java編譯器把所有的類(lèi)都打入一個(gè)扁平的jar包中,并且把jar包放到class path上,jvm可以動(dòng)態(tài)一句類(lèi)的路徑從中定位并且加載相關(guān)的類(lèi)。然后,這樣存在了幾個(gè)嚴(yán)重的問(wèn)題:
- 無(wú)法通過(guò)路徑指定版本。比如如果類(lèi)路徑上存在同一個(gè)庫(kù)的兩個(gè)版本,會(huì)發(fā)生什么。而大型項(xiàng)目中很常見(jiàn),它的不同組件使用同一個(gè)庫(kù)的不同版本:解決方法有
- 使用自定義ClassLoader來(lái)隔離: http://www.blogjava.net/landon/category/54860.html(值得注意的兩點(diǎn)是:公有接口可以由系統(tǒng)類(lèi)加載器加載,舊的類(lèi)的實(shí)例和class很難被卸載)。比如: elasticsearch中的插件加載機(jī)制,實(shí)現(xiàn)了自定義的classLoader 。 甚至可以用ClassLoader來(lái)實(shí)現(xiàn)熱部署:https://cloud.tencent.com/developer/article/1915650
- 或者是OSGI
- sofa-ark是動(dòng)態(tài)熱部署和類(lèi)隔離框架,支付寶開(kāi)源
-
類(lèi)路徑不支持顯式的依賴(lài):
image.png
Java9提供了一個(gè)新的單位: 模塊。通過(guò) module聲明,緊接著的是模塊的名字和主體的內(nèi)容,定義在特殊的文件:module-info.class
, 下圖可知,module的層級(jí)是高于package的。
使用命令可以指導(dǎo)哪些目錄和類(lèi)文件會(huì)被打包進(jìn)入生成的JAR文件中:
javac module-info.java xxx/yyyy/zzz.java -d target
jar cvfe xxxxx.jar xxx.yyy.zzz -C target
然后運(yùn)行執(zhí)行命令:
java --module-path xxxxx.jar --module moduleName.xxx.yyy.zzz
并發(fā)性
無(wú)論是基于 Future 的異步 API 還是反應(yīng)式異步 API,被調(diào)方法的概念體(conceptual body) 都在另一個(gè)線程中執(zhí)行,調(diào)用方很可能已經(jīng)退出了執(zhí)行,不在調(diào)用異常處理器的作用域內(nèi)。很明顯,這種非常規(guī)行為觸發(fā)的異常需要通過(guò)其他的動(dòng)作來(lái)處理。
CompletableFuture
和Scala的Future很類(lèi)似
反應(yīng)式編程
直接看《反應(yīng)式設(shè)計(jì)模式》就好了
函數(shù)式
個(gè)人了解較多,忽略。