這篇文章將為大家詳細講解有關(guān)java中的Clojure怎樣抽象并發(fā)性和共享狀態(tài),小編覺得挺實用的,因此分享給大家做個參考,希望大家閱讀完這篇文章后可以有所收獲。
成都創(chuàng)新互聯(lián)公司專業(yè)為企業(yè)提供天等網(wǎng)站建設(shè)、天等做網(wǎng)站、天等網(wǎng)站設(shè)計、天等網(wǎng)站制作等企業(yè)網(wǎng)站建設(shè)、網(wǎng)頁設(shè)計與制作、天等企業(yè)網(wǎng)站模板建站服務(wù),10年天等做網(wǎng)站經(jīng)驗,不只是建網(wǎng)站,更提供有價值的思路和整體網(wǎng)絡(luò)服務(wù)。
前言
在所有 Java 下一代語言中,Clojure 擁有最激進的并發(fā)性機制和功能。Groovy 和 Scala 都為并發(fā)性提供了改善的抽象和語法糖的一種組合,而 Clojure 堅持了它始終在 JVM 上提供獨一無二的行為的強硬立場。在本期 Java 下一代 中,我將介紹 Clojure 中眾多并發(fā)性選項的一部分。首先是為 Clojure 中易變的引用提供支撐的基礎(chǔ)抽象:epochal 時間模型。
Epochal 事件模型
或許 Clojure 與其他語言最顯著的區(qū)別與易變的狀態(tài)和值 密切相關(guān)。Clojure 中的值 可以是任何用戶感興趣的數(shù)據(jù):數(shù)字 42、映射結(jié)構(gòu) {:first-name "Neal :last-name "Ford"} 或某些更大型的數(shù)據(jù)結(jié)構(gòu),比如 Wikipedia?;緛碇v,Clojure 語言對待所有值就像其他語言對待數(shù)字一樣。數(shù)字 42 是一個值,您不能重新定義它。但可對該值應(yīng)用一個函數(shù),返回另一個值。例如,(inc 42) 返回值 43。
在 Java 和其他基于 C 的語言中,變量 同時持有身份和值,這是讓并發(fā)性在 Java 語言中如此難以實現(xiàn)的因素之一。語言設(shè)計人員在線程抽象之前創(chuàng)建了變量抽象,變量的設(shè)計沒有考慮為并發(fā)性增加的復雜性。因為 Java 中的變量假設(shè)只有單個線程,所以在多線程環(huán)境中,需要像同步塊這樣麻煩的機制來保護變量。Clojure 的設(shè)計人員 Rich Hickey 讓交織(complect) 這個古老的詞匯恢復了活力(交織這個詞被定義為 “纏繞或編織”),用于描述 Java 變量中的設(shè)計缺陷。
Clojure 將值 與引用 分開。在 Clojure 世界觀中,數(shù)據(jù)以一系列不變的值的形式存在,如圖 1 所示。
圖 1. epochal 時間模型中的值
圖 1 顯示,像 v1 這樣的獨立的值表示 42 或 Wikipedia 等數(shù)據(jù),使用方框表示。與值獨立的是函數(shù),它們獲取值作為參數(shù)并生成新值,如圖 2 所示。
圖 2. epochal 時間模型中的函數(shù)
圖 2 將函數(shù)顯示為與值獨立的圓圈。函數(shù)調(diào)用會生成新值,使用值作為參數(shù)和結(jié)果。一連串的值保存在一個引用 中,它表示變量的身份。隨著時間的推移,此身份可能指向不同的值(由于函數(shù)應(yīng)用),但身份從不更改,如圖 3 中的虛線所示。
圖 3. epochal 時間模型中的引用
在圖 3 中,整幅圖表示一個引用隨時間的變化。虛線是一個引用,它持有其生存期內(nèi)的一連串的值??稍谀硞€時刻向引用分配一個新的不變值;引用指向的目標可更改,而無需更改該引用。
在引用的生存期中,一個或多個觀察者(其他程序、用戶界面、任何對該引用持有的值感興趣的對象)將解除引用它,查看它的值(或許還執(zhí)行某種操作),如圖 4 所示。
圖 4. 解除引用
在圖 4 中,觀察者(有兩種楔形表示)可持有引用本身(由來自虛線引用的箭頭表示),或者可解除引用它,檢索它的值(由來自該值的箭頭表示)。例如,您可能有一個函數(shù),它以一個傳遞給您的數(shù)據(jù)庫連接作為參數(shù),您進而將該參數(shù)傳遞給一個更低級的持久性函數(shù)。在此情況下,您持有該引用,但從不需要它的值;持久性函數(shù)可能會解除引用它,以獲取它的值來連接到一個數(shù)據(jù)庫。
請注意,圖 4 中的觀察者不會進行協(xié)調(diào) — 它們完全不依賴彼此。此結(jié)構(gòu)使得 Clojure 運行時能夠在整個語言中保證了一些有用的屬性,比如決不允許讀取程序阻塞,這使得讀取操作變得非常高效。如果您希望更改一個引用(也就是說,將它指向一個不同的值),可使用 Clojure 的一個 API 來執(zhí)行更新,這會采用 epochal 時間模型。
epochal 時間模型為整個 Clojure 中的引用更新提供了支持。因為運行時控制所有更新,所以它可防御線程沖突,開發(fā)人員在不太復雜的語言中必須爭用線程。
Clojure 擁有廣泛的方式來更新引用,具體依賴于您想要何種特征。接下來,我將討論兩種方式:簡單的原子 和復雜的軟件事務(wù)內(nèi)存。
原子
Clojure 中的原子 是對數(shù)據(jù)一個原子部分的引用,無論該部分有多大。您創(chuàng)建一個 atom 并初始化它,然后應(yīng)用一個突變函數(shù)。這里,我為一個原子創(chuàng)建了一個稱為 counter 的引用,將它初始化為 0。如果我希望將引用更新到一個新值,我可使用 (swap!) 這樣的函數(shù),它原子化地為該引用換入一個新值:
(def counter (atom 0)) (swap! counter + 10)
根據(jù) Clojure 中的慣例,突變函數(shù)的名稱以一個感嘆號結(jié)尾。(swap!) 函數(shù)接受該引用、要應(yīng)用的函數(shù)(在本例中為 + 運算符)和任何其他參數(shù)。
Clojure 原子持有任何大小的數(shù)據(jù),而不只是原始值。例如,我可圍繞一個 person 映射創(chuàng)建一個原子引用,并使用 map 函數(shù)更新它。使用 (create-person) 函數(shù)(未顯示),我在一個原子中創(chuàng)建一個 person 記錄,然后使用 (swap!) 和 (assoc ) 更新該引用,這會更新一個映射關(guān)聯(lián):
(def person (atom (create-person))) (swap! person assoc :name "John")
原子還會通過 (compare-and-set!) 函數(shù),使用原子實現(xiàn)一個通用的樂觀鎖定模式:
(compare-and-set! a 0 42) => false (compare-and-set! a 1 7) = true
(compare-and-set!) 函數(shù)接受 3 個參數(shù):原子引用、想要的現(xiàn)有值和新值。如果原子的值與想要的值不匹配,更新不會發(fā)生,函數(shù)會返回 false。
Clojure 有各種各樣的機制都遵循引用語義。例如,promise(是一種不同的引用)承諾在以后提供一個值。這里,我創(chuàng)建對一個名為 number-later 的 promise 的引用。此代碼不會生成任何值,就像它對最終會這么做的承諾一樣。調(diào)用 (deliver ) 函數(shù)時,一個值會綁定到 number-later:
(def number-later (promise)) (deliver number-later 42)
盡管此示例使用了 Clojure 中的 futures 庫,但引用語義與簡單的原子保持一致。
軟件事務(wù)內(nèi)存
沒有其他任何 Clojure 特性獲得了比軟件事務(wù)內(nèi)存 (STM) 更多的關(guān)注,這是 Clojure 以 Java 語言封裝垃圾收集的方式來封裝并發(fā)性的內(nèi)部機制。換句話說,您可編寫高性能的多線程 Clojure 應(yīng)用程序,而從不考慮同步塊、死鎖、線程庫等。
Clojure 封裝并發(fā)性的方式是,通過 STM 控制引用的所有突變。更新一個引用(惟一的易變抽象)時,必須在一個事務(wù)中執(zhí)行,以使 Clojure 運行時能夠管理更新??紤]一個經(jīng)典的銀行問題:向一個帳戶中存款,同時向另一個帳戶貸款。清單 1 顯示了一個簡單的 Clojure 解決方案。
清單 1. 銀行交易
(defn transfer [from to amount] (dosync (alter from - amount) (alter to + amount)))
在 清單 1 中,我定義了一個 (transfer ) 函數(shù),它接受 3 個參數(shù):from 和 to 帳戶 — 二者都是引用 — 以及金額。我從 from 帳戶中減去該金額,將它添加到 to 帳戶中,但此操作必須與 (dosync ) 事務(wù)一起發(fā)生。如果我在事務(wù)塊的外部嘗試一個 (alter ) 調(diào)用,更新會失敗并拋出一個 IllegalStateException:
(alter from - 1) =>> IllegalStateException No transaction running
在 清單 1 中,(alter ) 函數(shù)仍然遵守 epochal 時間模型,但使用 STM 來確保兩個操作都完成或都未完成。為此,STM — 非常像一個數(shù)據(jù)庫服務(wù)器 — 臨時重試阻塞的操作,所以您的更新函數(shù)在更新之外不應(yīng)有任何副作用。例如,如果您的函數(shù)還寫入一個日志,由于不斷重試,您可能會看到多個日志條目。STM 還會隨未解決事務(wù)的時長增長而逐步提高它們的優(yōu)先級,顯示數(shù)據(jù)庫引擎中的其他更常見的行為。
STM 的使用很簡單,但底層機制很復雜。從名稱可以看出,STM 是一個事務(wù)系統(tǒng)。STM 實現(xiàn)了 ACID 事務(wù)標準的 ACI 部分:所有更改都是原子性、一致 和隔離的。ACID 的耐久 部分在這里不適用,因為 STM 在內(nèi)存中操作。很少看到將像 STM 這樣的高性能機制內(nèi)置于一種語言的核心中;Haskell 是惟一認真實現(xiàn)了 STM 的另一種主流語言 — 不要奇怪,因為 Haskell(像 Clojure 一樣)非常喜歡不變性。(.NET 生態(tài)系統(tǒng)曾嘗試構(gòu)建一個 STM 管理器,但最終放棄了,因為處理事務(wù)和不變性變得太復雜了。)
縮減程序(reducer)和數(shù)字分類
如果不討論 上一期 中的數(shù)字分類器問題的替代實現(xiàn),并行性介紹都是不完整的。清單 2 顯示了一個沒有并行性的原子版本。
清單 2. Clojure 中的數(shù)字分類器
(defn classify [num] (let [facts (->> (range 1 (inc num)) (filter #(= 0 (rem num %)))) sum (reduce + facts) aliquot-sum (- sum num)] (cond (= aliquot-sum num) :perfect (> aliquot-sum num) :abundant (< aliquot-sum num) :deficient)))
清單 2 中的分類器版本濃縮為單個函數(shù),它返回一個 Clojure 關(guān)鍵字(由一個前導冒號表示)。(let ) 塊使我能夠建立局部綁定。為了確定因數(shù),我使用 thread-last 運算符來過濾數(shù)字范圍,讓代碼更有序。sum 和 aliquot-sum 的計算都很簡單;一個數(shù)字的真因數(shù)和 是它的因數(shù)之和減去它本身,這使我的比較代碼更簡單。該函數(shù)的最后一行是 (cond ) 語句,它針對計算的值來計算 aliquot-sum,返回合適的關(guān)鍵字枚舉。此代碼的一個有趣之處是,我以前的實現(xiàn)中的方法在這個版本中折疊為簡單的賦值。在計算足夠簡單和簡潔時,您通常需要創(chuàng)建的函數(shù)更少。
Clojure 包含一個稱為 縮減程序 的強大的并發(fā)性庫。(有關(guān)縮減程序庫的開發(fā)過程的解釋 — 包括為利用最新的 JVM 原生的 fork/join 工具而進行的優(yōu)化 — 是一個吸引人的故事。)縮減程序庫提供了常見運算的就地替換,比如 map、filter 和 reduce,使這些預算能夠自動利用多個線程。例如,將標準的 (map ) 替換為 (r/map )(r/ 是縮減程序的命名空間),會導致您的映射操作自動被運行時并行化。
清單 3 給出了一個利用了縮減程序的數(shù)字分類器版本。
清單 3. 使用了縮減程序庫的分類器
(ns pperfect.core (:require [clojure.core.reducers :as r])) (defn classify-with-reducer [num] (let [facts (->> (range 1 (inc num)) (r/filter #(= 0 (rem num %)))) sum (r/reduce + facts) aliquot-sum (- sum num)] (cond (= aliquot-sum num) :perfect (> aliquot-sum num) :abundant (< aliquot-sum num) :deficient)))
必須仔細觀察,才能找出 清單 2 和 清單 3 之間的區(qū)別。惟一的區(qū)別是引入了縮減程序命名空間和別名,向 filter 和 reduce 都添加了 r/。借助這些細微的更改,我的過濾和縮減操作現(xiàn)在可自動使用多個線程。
關(guān)于“java中的Clojure怎樣抽象并發(fā)性和共享狀態(tài)”這篇文章就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,使各位可以學到更多知識,如果覺得文章不錯,請把它分享出去讓更多的人看到。
文章標題:java中的Clojure怎樣抽象并發(fā)性和共享狀態(tài)
網(wǎng)站URL:http://jinyejixie.com/article22/jjihcc.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供動態(tài)網(wǎng)站、做網(wǎng)站、面包屑導航、響應(yīng)式網(wǎng)站、網(wǎng)站策劃、關(guān)鍵詞優(yōu)化
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)