Java中Lambda表達(dá)式并行與組合行為
從串行到并行
串行指一個(gè)步驟一個(gè)步驟地處理,也就是通常情況下,代碼一行一行地執(zhí)行。
如果將我們常用的迭代器式的循環(huán)展開(kāi)的話,就是串行執(zhí)行了循環(huán)體內(nèi)所定義的操作:
sum += arr.get(0); sum += arr.get(1); sum += arr.get(2); //...
在書的一開(kāi)始,就提到Java需要支持集合的并行計(jì)算(而Lambda為這個(gè)需求提供了可能)。
這些功能將全部被實(shí)現(xiàn)于庫(kù)代碼中,對(duì)于我們使用者,實(shí)現(xiàn)并行的復(fù)雜性被大大降低(最低程度上只需要調(diào)用相關(guān)方法)。
另外,關(guān)于并發(fā)與并行這兩個(gè)概念,其實(shí)是不同的,如果不明白的話請(qǐng)自行了解,在此只引用一句非常流行的話:
一個(gè)是關(guān)于代碼結(jié)構(gòu),一個(gè)是關(guān)于代碼執(zhí)行。
如果我們想將一個(gè)計(jì)算任務(wù)均勻地分配給CPU的四個(gè)內(nèi)核,我們會(huì)給每個(gè)核分配一個(gè)用于計(jì)算的線程,每個(gè)線程上進(jìn)行整個(gè)任務(wù)的子任務(wù)。
書上有一段非常形象的偽代碼:
if the task list contains more than N/4 elements { leftTask = task.getLeftHalf() rightTask = task.getRightHalf() doInparallel { leftResult = leftTask.solve() rightResult = rightTask.solve() } result = combine(leftResult, rightResult) } else { result = task.solveSequentially() }
代碼中,將每四個(gè)任務(wù)元素分為一組,用四個(gè)內(nèi)核對(duì)其進(jìn)行并行處理,然后每?jī)山M進(jìn)行一次結(jié)果的合并,最終得到整個(gè)任務(wù)隊(duì)列的最終結(jié)果。
從整體處理流程上看,先將任務(wù)隊(duì)列遞歸地進(jìn)行分組,并行處理每一組,然后將結(jié)果遞歸地進(jìn)行合并(合并通過(guò)管道終止操作實(shí)現(xiàn))。
Java8之前,開(kāi)發(fā)者們使用一種針對(duì)集合的fork/join框架來(lái)實(shí)現(xiàn)該模式。
然而現(xiàn)在,想對(duì)代碼進(jìn)行性能優(yōu)化,就是一件非常容易的事了。
還記得我們上一節(jié)中所得出的最終代碼:
long validContactCounter = contactList.stream() .map(s -> new Contact().setName(s)) .filter(Contact::call) .count();
稍加改動(dòng):
long validContactCounter = contactList.parallelStream() .map(s -> new Contact().setName(s)) .filter(Contact::call) .count();
注意stream()變?yōu)閜arallelStream()
同時(shí)下圖將展示如何根據(jù)四個(gè)核對(duì)上述任務(wù)進(jìn)行分解處理,最終合并結(jié)果并終止管道。
注意遞歸分解的目的是使子任務(wù)們足夠小來(lái)串行執(zhí)行。
組合行為
Java寫手應(yīng)該知道,Java中并不存在純粹的“函數(shù)”,只存在“方法”。也就是說(shuō),Java中的函數(shù)必須依賴于某一個(gè)類,或者作為類的某種行為存在。
而在其他語(yǔ)言中,存在純函數(shù),以CoffeeScript的語(yǔ)法,聲明一個(gè)函數(shù):
eat = (x) -> alert("#{x} has been eatten!")
這種寫法與Lambda表達(dá)式的語(yǔ)法非常相近,也就是說(shuō),相比于匿名內(nèi)部類,Lambda表達(dá)式看上去更像是一種函數(shù)表達(dá)式。
對(duì)于函數(shù),一個(gè)核心操作便是組合。如果要求一元二次函數(shù)的其中一個(gè)解sqrt(sqr(b) - 4 * a * c),便是對(duì)多個(gè)子函數(shù)進(jìn)行了組合。
對(duì)于面向?qū)ο?,我們通過(guò)解耦的方式來(lái)分解它,同樣,我們也希望以此種方式分解一個(gè)函數(shù)行為。
首先,沿用上兩節(jié)中使用的例子,對(duì)Contact類稍作修改,將name屬性分拆為名和姓:
private String firstName; private String lastName;
假設(shè)我們現(xiàn)在想要對(duì)聯(lián)系人們進(jìn)行排序,創(chuàng)建自定義排序的Java標(biāo)準(zhǔn)方式是創(chuàng)建一個(gè)Comparator:
public interface Comparator<T> { int compare(T o1, T o2); //... }
我們想通過(guò)比較名的首字母來(lái)為聯(lián)系人排序:
Comparator<Contact> byFirstName = new Comparator<Contact>() { @Override public int compare(Contact o1, Contact o2) { return Character.compare(o1.getFirstName().charAt(0), o2.getFirstName().charAt(0)); } };
Lambda寫法:
Comparator<Contact> byFirstNameLambdaForm = (o1, o2) -> Character.compare(o1.getFirstName().charAt(0), o2.getFirstName().charAt(0));
寫完這段代碼后,IDEA立即提醒我代碼可以替換為Comparator.comparingInt(...),不過(guò)這是后話,暫且不表。
在上面的代碼中,我們發(fā)現(xiàn)了組合行為,即Comparator<Contact>
的compare(...)方法里面還套用了o.getFirstName()與Character.compare(...)這兩個(gè)方法(為了簡(jiǎn)潔,這里暫不考慮charAt(...)),在java.util.function中,我們找到了這種函數(shù)的原型:
public interface Function<T, R> { R apply(T t); //... }
接收一個(gè)T類型的參數(shù),返回一個(gè)R類型的結(jié)果。
現(xiàn)在我們將“比較名的首字母”這個(gè)比較鍵的提取行為抽成一個(gè)函數(shù)對(duì)象的實(shí)例:
Function<Contact, Character> keyExtractor = o -> o.getFirstName().charAt(0);
再將“比較首字母”這個(gè)具體的比較行為抽出來(lái):
Comparator<Character> keyComparator = (c1, c2) -> Character.compare(c1, c2);
有了keyExtractor和keyComparator,我們?cè)賮?lái)重新裝配一下Comparator:
Comparator<Contact> byFirstNameAdvanced = (o1, o2) -> keyComparator.compare(keyExtractor.apply(o1), keyExtractor.apply(o2));
到了這一步,我們犧牲了簡(jiǎn)潔性,但獲得了相應(yīng)的靈活性,也就是說(shuō),如果我們改變比較鍵為姓而非名,只需改動(dòng)keyExtractor為:
Function<Contact, Character> keyExtractor = o -> o.getLastName().charAt(0);
值得慶幸的是,庫(kù)的設(shè)計(jì)者考慮到了這一自然比較的需求的普遍性,因此為Comparator接口提供了靜態(tài)方法comparing(...),只需傳入比較鍵的提取規(guī)則,就能針對(duì)該鍵生成相應(yīng)的Comparator,是不是非常神奇:
Comparator<Contact> compareByFirstName = Comparator.comparing(keyExtractor);
即使我們想改變比較的規(guī)則,比如比較聯(lián)系人姓與名的長(zhǎng)度,也只需做些許改動(dòng):
Comparator<Contact> compareByNameLength = Comparator.comparing(p -> (p.getFirstName() + p.getLastName()).length());
這是一個(gè)重大的改進(jìn),它將我們所關(guān)注的焦點(diǎn)真正集中在了比較的規(guī)則上面,而不是大量地構(gòu)建所必須的膠水代碼。
comparing(...)通過(guò)接收一個(gè)簡(jiǎn)單的行為,進(jìn)而基于這個(gè)行為構(gòu)造出更加復(fù)雜的行為。
贊!
然而更贊的是,對(duì)于流和管道,我們所需要的改動(dòng)甚至更少:
contacts.stream() .sorted(compareByNameLength) .forEach(c -> System.out.println(c.getFirstName() + " " + c.getLastName()));
小結(jié)
本章的代碼:
import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.function.Function; public class Bar { public static void main(String[] args) { // long validContactCounter = contactList.parallelStream() // .map(s -> new Contact().setFirstName(s)) // .filter(Contact::call) // .count(); List<Contact> contacts = new ArrayList<Contact>() {{ add(new Contact().setFirstName("Foo").setLastName("Jack")); add(new Contact().setFirstName("Bar").setLastName("Ma")); add(new Contact().setFirstName("Olala").setLastName("Awesome")); }}; Comparator<Contact> byFirstName = new Comparator<Contact>() { @Override public int compare(Contact o1, Contact o2) { return Character.compare(o1.getFirstName().charAt(0), o2.getFirstName().charAt(0)); } }; //--- Using Lambda form ---// Comparator<Contact> byFirstNameLambdaForm = (o1, o2) -> Character.compare(o1.getFirstName().charAt(0), o2.getFirstName().charAt(0)); Function<Contact, Character> keyExtractor = o -> o.getFirstName().charAt(0); Comparator<Character> keyComparator = (c1, c2) -> Character.compare(c1, c2); Comparator<Contact> byFirstNameAdvanced = (o1, o2) -> keyComparator.compare(keyExtractor.apply(o1), keyExtractor.apply(o2)); Comparator<Contact> compareByFirstName = Comparator.comparing(keyExtractor); Comparator<Contact> compareByNameLength = Comparator.comparing(p -> (p.getFirstName() + p.getLastName()).length()); contacts.stream() .sorted(compareByNameLength) .forEach(c -> System.out.println(c.getFirstName() + " " + c.getLastName())); } }
以及運(yùn)行結(jié)果:
Bar Ma Foo Jack Olala Awesome
以上所述是小編給大家介紹的Java中Lambda表達(dá)式并行與組合行為,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
相關(guān)文章
SpringBoot深入分析講解監(jiān)聽(tīng)器模式上
監(jiān)聽(tīng)器模式,大家應(yīng)該并不陌生,主要的組成要素包括了事件、監(jiān)聽(tīng)器以及廣播器;當(dāng)事件發(fā)生時(shí),廣播器負(fù)責(zé)將事件傳遞給所有已知的監(jiān)聽(tīng)器,而監(jiān)聽(tīng)器會(huì)對(duì)自己感興趣的事件進(jìn)行處理2022-07-07springboot prototype設(shè)置多例不起作用的解決操作
這篇文章主要介紹了springboot prototype設(shè)置多例不起作用的解決操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-09-09java根據(jù)當(dāng)前時(shí)間獲取yyyy-MM-dd?HH:mm:ss標(biāo)準(zhǔn)格式的時(shí)間代碼示例
在Java中可以使用java.time包中的LocalDateTime類和DateTimeFormatter類來(lái)獲取并格式化當(dāng)前時(shí)間為yyyy-MM-dd?HH:mm:ss的格式,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-10-10Spring sentinel哨兵模式相關(guān)原理解析
這篇文章主要介紹了Spring sentinel哨兵模式相關(guān)原理解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-11-11Java模擬登錄正方教務(wù)抓取成績(jī)、課表、空教室
這篇文章主要介紹了Java模擬登錄正方教務(wù)抓取成績(jī)、課表、空教室等信息,Java實(shí)現(xiàn)模擬登錄正方教務(wù)抓取成績(jī)、課表、空教室,通過(guò)HttpClient來(lái)模擬瀏覽器請(qǐng)求,Jsoup解析網(wǎng)頁(yè)內(nèi)容,感興趣的小伙伴們可以參考一下2016-04-04SpringMVC實(shí)現(xiàn)文件上傳和下載的工具類
這篇文章主要為大家詳細(xì)介紹了SpringMVC實(shí)現(xiàn)文件上傳和下載的工具類,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-05-05解決復(fù)制springboot項(xiàng)目后,啟動(dòng)日志無(wú)顏色的問(wèn)題
這篇文章主要介紹了解決復(fù)制springboot項(xiàng)目后,啟動(dòng)日志無(wú)顏色的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07SpringSecurity實(shí)現(xiàn)動(dòng)態(tài)url攔截(基于rbac模型)
本文主要介紹了SpringSecurity動(dòng)態(tài)url攔截,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-08-08