您好,登錄后才能下訂單哦!
小編給大家分享一下Java語言中并發性選項的區別是什么,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!
前言
Java? 工程師在努力讓并發性容易為開發人員所用。盡管做了不少的改進,但并發性仍然是 Java 平臺的一個復雜、容易出錯的部分。一部分復雜之處在于理解語言本身中的并發性的低級抽象,這些抽象在您的代碼中填滿了同步的代碼塊。另一個復雜之處來自一些新庫,比如 fork/join,這些庫在某些場景中非常有用,但在其他場景中收效甚微。了解容易混亂的大量低級選項需要專業經驗和時間。
脫離 Java 語言的優勢之一是,能夠改善和簡化并發性等區域。每種 Java 下一代語言都為此問題提供了獨特的答案,利用了該語言的默認編程風格。在本期文章中,我首先將會介紹函數式編程風格的優勢:輕松并行化。我會深入分析 Scala 和 Groovy 的細節(下一期文章將全面介紹 Clojure)。然后介紹 Scala actor。
完美數
數學家尼科馬庫斯(誕生于公元前 6 世紀)將自然數分為惟一的完美數(perfect number)、過剩數(abundant number) 或虧數(deficient number)。一個完美數等于它的正因數(不包括它本身)之和。例如,6 是一個完美數,因為它的因數是 1、2、3 和 6,28 也是完美數 (28 = 1 + 2 + 4 + 7 + 14)。過剩數的因素之和大于該數,虧數的因數之和小于該數。
這里使用完美數分類法是為了方便介紹。除非要處理大量數字,是否查找因素對于從并行化中獲益而言是一個微不足道的問題。使用更多線程可帶來一些益處,但線程之間的切換開銷對細粒度的作業而言代價很高。
讓現有代碼并行化
在 “函數式編碼風格” 那一期的文章中,我們鼓勵您使用更高級的抽象,比如化簡、映射和過濾器,而不是迭代。此方法的優勢之一是容易并行化。
我的 函數式思維 系列的讀者熟悉包含完美數 的數字分類模式(參見 完美數 邊欄)。我在該系列中展示的任何解決方案都沒有利用并發性。但是因為這些解決方案使用了轉換函數,比如 map,所以我可以在每種 Java.net 語言中做極少的工作來創建并行化的版本。
清單 1 是完美數分類器的一個 Scala 示例。
清單 1. Scala 中的并行完美數分類器
object NumberClassifier { def isFactor(factor: Int, number: Int) = number % factor == 0 def factors(number: Int) = { val factorsBelowSqrt = (1 to Math.sqrt(number).toInt).par.filter (isFactor(_, number)) val factorsAboveSqrt = factorsBelowSqrt.par.map(number / _) (factorsBelowSqrt ++ factorsAboveSqrt).toList.distinct } def sum(factors: Seq[Int]) = factors.par.foldLeft(0)(_ + _) def isPerfect(number: Int) = sum(factors(number)) - number == number }
清單 1 中的 factors() 方法返回一個數的因數列表,使用 isFactor() 方法過濾所有可能的值。factors() 方法使用了我在 “函數式思維:轉換和優化” 中更詳細地介紹的一種優化。簡單來講,過濾每個數來查找因素的效率很低,因為根據定義,一個因數是其乘積等于目標數的兩個數之一。
相反,我僅過濾不超過目標數的平方根的數,然后通過將目標數除以每個小于平方根的因數來生成對稱因數列表。在 清單 1 中,factorsBelowSqrt 變量包含過濾操作的結果。factorsAboveSqrt 的值是現有列表的映射,用于生成這些對稱值。最后,factors() 的返回值是一個串聯的列表,它從一個并行的List 轉換為常規的 List。
請注意,清單 1 中添加了 par 修飾符。該修飾符會導致 filter、map 和 foldLeft 并行運行,從而能夠使用多個線程來處理請求。par 方法(在整個 Scala 集合庫中都是一致的)將該序列轉換為并行序列。因為兩種類型的序列反映了它們的簽名,所以 par 函數變成了并行化某個操作的臨時方式。
在 Scala 中并行化常見問題的簡單性,在語言設計和函數模式上都經過證實。函數式編程鼓勵使用通用的函數,比如 map、filter 和 reduce,運行時以不可見的方式可以進一步優化它們。Scala 語言設計人員考慮到了這些優化,最終產生了集合 API 的設計。
邊緣情況
在 清單 1 的 factors() 方法實現中,整數的平方根(例如,16 的平方根:4)顯示在兩個列表中。因此,factors() 方法返回的最后一行是對 distinct 函數的調用,它從列表中刪除了重復值。您也可以在每一處都使用 Set,而不是只在列表中使用它,但 List 常常擁有 Set 中所沒有的有用函數。
Groovy 也允許輕松地修改現有的函數代碼,通過 GPars 庫讓它并行化,該庫捆綁在各個 Groovy 發行版中。GPars 框架在內置的 Java 并行性原語之上創建有用的抽象,常常將它們包裝在語法糖中。GPars 提供了令人眼花繚亂的并行機制,其中一種機制可用于分配線程池,然后將操作分布到這些池中。清單 2 中給出了一個使用 Groovy 編寫的,使用 GPars 線程池的完美數分類器。
清單 2. Groovy 中的并行完美數分類器
class NumberClassifierPar { static def factors(number) { GParsPool.withPool { def factors = (1..round(sqrt(number) + 1)).findAllParallel { number % it == 0 } (factors + factors.collectParallel { number / it }).unique() } } static def sumFactors(number) { factors(number).inject(0, { i, j -> i + j }) } static def isPerfect(number) { sumFactors(number) - number == number } }
清單 2 中的 factors() 方法使用了與 清單 1 相同的算法:它生成不超過目標數的平方根的所有因數,然后生成剩余的因數并返回串聯的集合。與 清單 1 中一樣,我使用 unique() 方法來確保整數的平方根不會生成重復值。
無需像 Scala 中一樣放大集合來創建對稱并行版本,Groovy 的設計人員創建了該語言的轉換方法的 xxxParallel() 版本(例如 findAllParallel() 和 collectParallel())。但除非這些方法包裝在 GPars 線程池代碼塊中,否則它們不會起作用。
在 清單 2 中,我創建了一個線程池,調用 GParsPool.withPool 創建一個代碼塊,支持在該代碼塊中使用 xxxParallel() 方法。withPool 方法存在其他變體。例如,您可指定池中的線程數量。
Clojure 通過 化簡器 庫提供了一種類似的臨時并行化機制。使用轉換函數的化簡器版本來實現自動并行化,例如,
使用 r/map 代替 map。(r/ 是化簡器命名空間。)化簡器的實現是 Clojure 的語法靈活性中的一個引人注目的案例分析,它通過極小的更改實現了強大的添加功能。
Scala 中的 actor
Scala 包含眾多并發性和并行性機制。一種較流行的機制是 actor 模型,它提供了將工作分布到線程上的優勢,而沒有同步的復雜性。在概念上,actor 有能力完成工作,然后將一個非阻塞的結果發送給協調器。要創建一個 actor,需要創建 Actor 類的子類并實現 act() 方法。通過使用 Scala 的語法糖,可繞過許多定義儀式,在代碼塊內定義 actor。
我沒有為 清單 1 中的數字分類器執行的一種優化是,使用線程對作業的因數查找部分進行分區。如果我的計算機上有 4 個處理器,我可為每個處理器創建一個線程并拆分工作。例如,如果我嘗試找到數字 16 的因數之和,那么我可以安排處理器 1 來查找從 1 到 4 的因數(并求和),安排處理器 2 來處理 5 到 8,依此類推。使用 actor 是一種自然的選擇:我為每個范圍創建了一個 actor,獨立地執行每個 actor(通過語法糖隱式執行或通過調用它的 act() 方法來顯式執行),然后收集結果,如清單 3 所示。
清單 3. 使用 Scala 中的 actor 識別完美數
object NumberClassifier extends App { def isPerfect(candidate: Int) = { val RANGE = 10000 val numberOFPartitions = (candidate.toDouble / RANGE).ceil.toInt val coordinator = self for (i <- 0 until numberOFPartitions) { val lower = i * RANGE + 1 val upper = candidate.min((i + 1) * RANGE) actor { var partialSum = 0 for (j <- lower to upper) if (candidate % j == 0) partialSum += j coordinator ! partialSum } } var responsesExpected = numberOFPartitions var sum = 0 while (responsesExpected > 0) { receive { case partialSum : Int => responsesExpected -= 1 sum += partialSum } } sum == 2 * candidate } }
為了保持此示例的簡單性,我將 isPerfect() 編寫為單個完整的函數。我首先基于常量 RANGE 創建了一些分區。其次,我需要一種方式來收集 actor 所生成的消息。在 coordinator 變量中,我有一個引用可供 actor 向其發送消息,其中 self 是 Actor 的一個成員,表示 Scala 中獲取線程標識符的可靠方式。
我然后為分區編號創建一個循環,使用 RANGE 偏移來生成范圍的下限和上限。接下來,為該范圍創建一個 actor,使用 Scala 的語法糖來避免正式的類定義。在 actor 內,我為 partialSum 創建了一個臨時保存器,然后分析該范圍,將找到的因數收集到 partialSum 中。收集部分和(此范圍內的所有因數的和)后, (coordinator ! partialSum) 向協調器發回一條消息,使用感嘆號運算符。(這種消息傳遞語法的靈感來源于 Erlang 語言,用作一種對另一個線程執行非阻塞調用的途徑。)
接下來,我啟動了一個循環,等待所有 actor 完成處理。在等待過程中,我進入了一個 receive 代碼塊。在該代碼塊內,我想要一條 Int 消息,我在本地將它分配給 partialSum,然后遞減想要的響應數量,將該部分添加到總和中。所有 actor 完成且報告結果后,該方法的最后一行將該和與候選數的 2 倍相比較。如果比較結果為 true,那么我的候選數就是一個完美數,該函數的返回值為 true。
actor 的一個不錯的優勢是所有權分區。每個 actor 都有一個 partialSum 局部變量,但它們從不彼此聯系。通過協調器收到消息時,底層執行機制是不可見的:您創建了一個 receive 塊,其他實現細節是不可見的。
Scala 中的 actor 機制是 Java 下一代語言封裝 JVM 的現有工具并使用一致的抽象來擴展它們的優秀示例。用 Java 語言編寫類似的代碼,并使用低級并發性原語,這些操作都需要非常復雜地協調多個線程。Scala 中的 actor 隱藏了所有復雜性,留下的是容易理解的抽象。
以上是“Java語言中并發性選項的區別是什么”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。