Java 8中的并行和异步编程
并行代碼是在多個線程上運行的代碼,曾經是許多經驗豐富的開發人員的噩夢,但是Java 8帶來了許多更改,這些更改應該使這種提高性能的技巧更加易于管理。
并行流
在Java 8之前,并行(或并發)代碼與順序代碼之間存在很大差異。 調試非順序代碼也非常困難。 只需像通常那樣設置一個斷點并按照流程進行操作,就可以刪除并行方面,如果這是導致該錯誤的原因,那么這是一個問題。
幸運的是,Java 8為我們提供了流,這是自Bean以來對Java開發人員而言最大的事情。 如果您不知道它們是什么,則Stream API可以處理功能問題中的元素序列。 (在這里檢查流與.NET的LINQ之間的比較。)流的優點之一是代碼的結構保持不變:無論是順序的還是并發的,它都保持可讀性。
為了使您的代碼并行運行,您只需使用.parallelStream()而不是.stream() (或者,如果您不是流的創建者,則可以使用stream .parallel() )。
但是,僅僅因為它很容易,并不意味著并行代碼始終是最佳選擇。 您應該始終考慮對代碼使用并發是否有意義。 該決定中最重要的因素將是速度:僅當并發使您的代碼比其順序對應的代碼更快時才使用并發。
速度問題
并行代碼通過使用多個線程而不是順序代碼使用的單個線程而獲得了速度優勢。 確定創建多少個線程可能是一個棘手的問題,因為更多的線程并不總是會導致更快的代碼:如果使用太多的線程,則代碼的性能實際上可能會下降。
有幾個規則可以告訴您選擇多少線程。 這主要取決于您要執行的操作類型以及可用內核的數量。
計算密集型操作應使用少于或等于內核數的線程數,而IO密集型操作(如復制文件)對CPU無用,因此可以使用更多線程。 該代碼不知道哪種情況適用,除非您告訴它該怎么做。 否則,它將默認為線程數等于內核數。
在兩種主要情況下,并行運行代碼而不是順序運行代碼很有用:耗時的任務和在大集合上運行的任務。 Java 8帶來了一種處理大集合的新方法,即使用流。 流具有惰性的內置效率:它們使用惰性評估,通過不做不必要的事情來節省資源。 這與并行性不同,只要它運行得更快,它就不會在乎資源。 因此,對于大型集合,您可能不需要經典的并行性。
================================================== ====================
異步
JavaScript的教訓
Java開發人員很少會說他們從看過JavaScript中學到了什么,但是在異步編程方面,JavaScript實際上是正確的。 作為一種從根本上異步的語言,JavaScript經驗豐富,如果實施不好,它會帶來多大的痛苦。 它從回調開始,后來被promise取代。 許諾的一個重要好處是它有兩個“通道”:一個用于數據,另一個用于錯誤。 JavaScript承諾可能看起來像這樣:
func .then(f1) .catch(e1) .then(f2) .catch(e2);因此,當原始函數獲得成功結果時,將調用f1,但是如果引發錯誤,則將調用e1。 這可能會將其帶回到成功軌道(f2),或導致另一個錯誤(e2)。 您可以從數據跟蹤到錯誤跟蹤再返回。
JavaScript承諾的Java版本稱為CompletableFuture 。
未來發展
CompletableFuture同時實現Future和CompletionStage接口。 Java8之前就已經存在Future ,但是它本身對開發人員并不十分友好。 您只能使用.get()方法獲得異步計算的結果,該方法會阻塞其余部分(使異步部分大部分時間變得毫無意義),并且您需要手動實現每種情況。 添加CompletionStage接口是使Java異步編程可行的突破。
CompletionStage是一個承諾,即最終將完成計算的承諾。 它包含許多方法,可讓您附加將在完成時執行的回調。 現在我們可以處理結果而不會阻塞。
有兩種主要方法可讓您啟動代碼的異步部分: supplyAsync如果您想對方法的結果做一些事情)和runAsync如果您不想這樣做)。
CompletableFuture.runAsync(() → System.out.println("Run async in completable future " + Thread.currentThread())); CompletableFuture.supplyAsync(() → 5);回呼
現在,您可以添加這些回調以處理supplyAsync的結果
CompletableFuture.supplyAsync(() → 5) .thenApply(i → i * 3) .thenAccept(i → System.out.println(“The result is “ + i) .thenRun(() → System.out.println("Finished."));.thenApply與.thenApply的.map函數相似:它執行轉換。 在上面的示例中,它將結果(5)乘以3。然后將結果(15)進一步傳遞給管道。
.thenAccept對結果執行一種方法,而無需對其進行轉換。 它也不會返回結果。 它將在控制臺上顯示“結果為15”。 可以將它與.foreach方法進行流比較。
.thenRun不使用異步操作的結果,也不返回任何內容,它只是等待調用其Runnable直到上一步完成為止。
異步處理
上述所有回調方法也都具有異步版本: thenRunAsync , thenApplyAsync等。這些版本可以在自己的線程上運行,它們為您提供了額外的控制權,因為您可以告訴它要使用哪個ForkJoinPool 。
如果您不使用異步版本,則所有回調將在同一線程上執行。
當事情出錯時
當出現問題時,將使用exceptionally方法來處理異常。 您可以給它提供一個方法,該方法返回一個值以返回數據軌道,或引發(新)異常。
… .exceptionally(ex → new Foo()) .thenAccept(this::bar);合并和撰寫
您可以使用thenCompose方法鏈接多個CompletableFutures 。 沒有它,結果將嵌套CompletableFutures 。 這使得thenCompose和thenApply像flatMap和map用于流。
CompletableFuture.supplyAsync(() -> "Hello") .thenCompose(s -> CompletableFuture .supplyAsync(() -> s + "World"));如果要合并兩個CompletableFutures的結果,則需要一個方便地稱為thenCombine的方法。
future.thenCombine(future2, Integer::sum) .thenAccept(value → System.out.println(value));如您在上面的示例中看到的那樣,可以使用所有喜歡的CompletionStage方法像普通的CompletableFuture一樣處理thenCombine中的回調結果。
結論
在尋求更快的代碼時,并行編程不再是不可克服的障礙。 Java 8使該過程盡可能簡單明了,因此,可能從中受益的任何代碼都可以在所有線程上被拉,踢和尖叫,進入多核未來,而實際上,這只是現在天。 我的意思是:這很容易做到,因此請嘗試一下,看看自己的優勢。
翻譯自: https://www.javacodegeeks.com/2018/04/parallel-and-asynchronous-programming-in-java-8.html
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的Java 8中的并行和异步编程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: hibernate状态_Hibernat
- 下一篇: meta是什么意思中文_专访 | 从艾希