程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 適用於 Java 程序員的 CSP ,第 3 部分

適用於 Java 程序員的 CSP ,第 3 部分

編輯:關於JAVA

如果您從開始就一直閱讀這個系列,那麼到了現在,您應當同意 CSP 是一個值得研究的編程范式,而 JCSP 庫則值得您添加到工具包中。在第 3 部分中,我只是給您提供了更多的研究 JCSP 理由。我將從討論 CSP 的復合技術與面向方面編程(或 AOP) 的模塊化技術之間的相似性開始。然後介紹 JCSP 的一些更高級的同步構造,並討論適合應用它們的場景,還將更廣泛地討論使用 JCSP 進行多線程編程的好處,比如驗證、維護以及可應用於分布式編程的一種方法。

我還要回答以下問題:如果已經采用了 Java.util.concurrent 的並發性工具,那麼是否還有學習 JCSP 的必要。(顯然,我認為有這個必要!) http://www.mscto.com

如果還沒有閱讀本文的 第 1 部分 和 第 2 部分,那麼在繼續我們的介紹之前,您可能需要這麼做。

AOP 和 CSP
面向方面編程,即 AOP,是現在大多數 Java 開發人員都知道的一個術語。AOP 把系統當成方面或者關注點的的組合,而不是單純地將系統看成是對象的組合。它試圖通過把切入點組合成獨立的模塊,由模塊代碼解決一個特定關注,從而提高系統的模塊性。這些模塊可以用靜態或動態編織技術編織在一起。 http://www.mscto.com

這個系列文章中已經介紹了很多內容,所以對您來說,應當很容易就可以看出 AOP 的概念 把模塊編織在一起 與 CSP 的概念 組合進程網絡 之間的相似性。兩個技術之間的相似(絕對不是故意的!)主要是由於後者的復合性質。

一個基於安全性檢測的示例被頻繁地用作 AOP 的示例。在這個示例中,包含解決應用程序安全問題的代碼的模塊是以方面的形式編寫和打包的;然後,這個方面將應用於整個應用程序中所有適當的業務對象。為了用 CSP 解決同樣的問題,可以先從編寫進程開始,編寫一個包含業務對象的功能的進程,再編寫另一個包含應用程序的安全性檢測功能的進程。然後可以用 One2One 通道把兩個進程連接起來。CSP 技術的優勢與 AOP 技術類似:都可以將關注點分隔開。

不要錯過本系列其余部分!
“適用於 Java 程序員的 CSP”是由三部分組成的介紹通信順序進程(Communicating Sequential Processes —— CSP)的一個系列。CSP 是並發編程的一個范式,它承認了並發編程的復雜性,卻沒有把復雜性留給開發人員。請閱讀本系列的其他部分:

第 2 部分:用 JCSP 進行並發編程

第 3 部分: JCSP 的高級主題

軟件開發網

高級 JCSP 同步
通道不是進程間同步的惟一可用選擇,也不總是最合適的選擇。通道總是在以下兩個進程間同步:閱讀器和寫入器。是否在運行多個閱讀器/寫入器並不重要,因為在 Any2OneOne2AnyAny2Any 模型中,每種類型的進程都只有一個進程能夠積極地參與到通道兩邊進行的會話中。 軟件開發網

為了獲得更高級的同步,即在多個進程之間的同步,JCSP 提供了 BarrIErBucketCREW 構造,還提供了一個叫作 ProcessManager 的類負責管理 CSProcess 實例。

BarrIEr 構造
barrIEr 是一個 JCSP 事件,可以充當多個進程之間的同步機制。每個 barrier 都能與多個進程相關聯,任何在這個 barrIEr 上同步的進程都將被阻塞,直到其他進程已經同步為止。

必須用需要在 BarrIEr 類上同步的進程的數量來創建它,其中每個進程都運行在獨立的線程中。在完成當前一輪工作之後,每個進程都調用 BarrIEr 實例的 sync() 方法,然後等候其他進程完成。 軟件開發網

BarrIEr 內部
從內部來看,BarrIEr 包含一個成員變量,在創建 BarrIEr 對象時,這個成員變量被初始化為指定的進程總數。每當進程調用 sync() 方法時,這個變量就減一,並發出 wait() 調用。(您會想起已經介紹過的內容:Java 線程在能夠調用 wait() 方法之前必須一直持有恰當的鎖。在這裡,由於將 BarrIErsync() 方法標記為同步的,所以 BarrIEr 實例本身充當著鎖的作用。) 檢測是在最後一個調用 sync() 的進程所在的線程上進行的。如果成員變量變成 0,則清楚地表明,所有進程已經完成了對 sync() 的調用 ,可以安全的發出 notifyAll() 了。這時,內部的計數器被重新設置回最初提供的進程總數,從而使得 BarrIEr 實例可以在下一輪同步中重復使用。

注意,BarrIEr 並沒有把進程的身份和自身關聯起來。所以,只有能得到 BarrIEr 對象,任何進程(除了要在 BarrIEr 上同步的進程之外)都能調用對象上的sync,從而將一個或多個合法進程排除在外。要想防止這個問題,可以對 BarrIEr 對象的可見性進行嚴格控制,只允許那些已經在 BarrIEr 上同步的進程集看到它。

似曾相識!
如果您認為 BarrIEr 工作方式的描述聽起來很熟悉,那就對了。Parallel 構造自行控制運行多個 CSProcess 實例的基礎就是 BarrIEr 構造的底層機制。

您會想起前面介紹過,在您調用 Parallel 實例上的 run 方法時,它創建了 (n-1) 個線程來運行前面 (n-1)CSProcess 實例,然後在自己的線程中運行最後一個 CSProcess 實例。所有這些進程都在一個公共的 BarrIEr 實例上進行 sync,這個實例是在 Parallel 類的構造函數中創建的(而且通過負責運行 CSProcess 的每個 n-1 線程的構造函數,可以將實例傳遞給每個線程)。

BarrIEr 是一個比 Parallel 級別低的構造,因此,相應地也就帶來了更多的復雜性,進程可以在任意時間從 BarrIEr 對象 enroll(登記)或 resign(退出)。例如,在一個工作進程運行時,它可能想與時鐘同步(實際上是模擬時鐘滴答) ,然後停下來休息一段時間(在此期間,它應當從 barrier 退出),接著再登記到 barrIEr,重新開始工作。 軟件開發網

Bucket 構造
bucket 也是一個用於多進程的同步 barrier。bucket 構造和 barrIEr 構造之間的區別是:在前者中,所有進程都被阻塞,直到另外一個(外部)進程決定釋放它們,而釋放是通過“清空 bucket”進行的。

使用 Bucket 的方法只是創建一個 bucket 構造;然後,每個需要在該構造上面同步的進程都可以調用這個 bucket 的 fallInto() 方法。fallInto() 是一個同步的方法,它維護一個內部變量,用該變量來跟蹤當前(等候)的進程數量(也就是說,每有一個調用fallInto() 方法的進程,計數器變量就加 1。)在計數增加之後,調用進程執行 wait(),這導致調用進程受阻塞。當外部進程調用 Bucket 的(也是同步的) flush() 方法時,就發送一個 notifyAll() ,喚醒所有受阻塞的線程。阻塞進程的最後計數將從這個調用中輸出。 軟件開發網

BarrIEr 是確定性的,而 Bucket 是非確定性的。可以保證同步到 BarrIEr 的進程得到重新安排,從而得以執行(在一個時間周期後,受阻時間長短等於組中最慢的進程的執行時間),同步到 Bucket 的線程受阻塞的時間是不確定的,時間長短取決於清除進程何時決定發出釋放這些進程的調用。

在必須按照先後順序處理一組進程時,BarrIEr 很有用。這就是說,每個進行到步驟 n 的進程都不能進行步驟 n 1,直到同組中的其他所有進程都已經完成它們的步驟 n。在每個進程都要完成自己分配的任務並且停在 bucket 中,以表明自己可以進行下一步,這種情況下,BarrIEr 很有用。清除進程可以周期性地清除這個 bucket (可能用某些工作調度算法),從而釋放目前在 bucket 中的所有進程,開始下一組任務。

CREW 構造
並發讀/排它寫(Concurrent-Read-Exclusive-Write)構造,即 CREW 鎖,允許多個並行閱讀器訪問共享資源,條件是:(1)“讀”訪問不會修改資源;(2)之前沒有寫入器正在訪問資源。當寫入器正在訪問資源時,它對資源擁有排它權限:不管是閱讀器還是寫入器,都不允許訪問資源。

CREW 鎖的使用是為了消除爭用風險(因為競爭的閱讀器/寫入器進程之間任意的交叉),並且由於允許多個閱讀器進程並行執行而提高了性能。是否真的會有性能提升則取決於以下因素:以讀模式訪問資源的頻率與以寫模式訪問資源的頻率的比較、讀操作和寫操作的時間,以及在同一時間在沖突模式下試圖訪問共享資源的進程數量。

請參閱 JCSP 發行版附帶的 Javadoc(在 參考資料中),以獲得 CREW 鎖的內部實現的精彩描述。

管理 CSProcess 實例
在 第 2 部分 中,我介紹了如何用 Parallel 構造把較低級別的“構造塊”進程組合成更高級別的進程網絡。雖然對於許多編程場景,這些描述都是合適的,但在這篇文章的示例中,我假設已經知道了需要創建的所有進程的全部細節。但是對於某些應用程序來說,可能會發現有必要根據只能在運行時才能計算的某些條件,動態地創建一個或多個進程,而且還應當能夠管理它們。

JCSP.Net
我在本文示例中使用的 JCSP 庫實際上使用的是同一 JVM 中運行的線程,當然,JVM 可能正運行在單處理器或多處理器機器上。倘若您對是否有可能組合對跨許多 JVM 的進程進行通信和同步的分層網絡感到懷疑,那麼答案是肯定的!JCSP 庫的叫作 JCSP.Net 的一個變體可以讓您精確地做到這點。使用 JCSP.net 編程的最大好處是:跨物理網絡運行的虛擬通道看起來差不多就像您在本文中已經遇到過的本地的、單一 JVM 的通道一樣,雖然很顯然會更復雜一些。請參閱 參考資料,學習關於 JCSP.Net 可用的商業版本的更多內容。

JCSP 為這類場景提供了一個叫作 ProcessManager 的類。這個類接受 CSProcess 實例作為輸入參數,允許用以下兩種不同的方法之一啟動進程。如果在ProcessManager 上調用 run() 方法,那麼 ProcessManager
將啟動托管的進程,然後等候進程終止(被管理的進程在 ProcessManager 的線程上運行,所以,後者的運行被阻擋,直到托管的進程完成為止)。另一個選項是調用 ProcessManager 上的 start() 方法,這使托管的進程在獨立的線程中啟動,在這種情況下,ProcessManager 自己仍然繼續運行。

管理並行優先級
我在 第 2 部分 中提到:Parallel 構造並行地運行構造函數中傳遞給它的數組中包含的所有 CSProcess 實例。但是在某些情況下,可能必須要為這些進程賦予不同的優先級。JCSP 提供了一個叫作 PriParallel 的類,可以為傳遞給它的進程加上優先級。 http://www.mscto.com

受控制的進程的優先級是由進程在數組中的位置索引決定的。在數組中,放在前面的進程擁有更高的優先級。注意,JCSP 以底層線程的優先級機制實現進程的優先級;所以優先級實際的工作方式取決於底層的 JVM 實現。

越來越多的構造!
迄今為止討論的構造(從第 2 部分開始到現在這裡)都是 JCSP 庫的核心構造。可以用它們來解決普通的和高級的並發性問題,現在應當可以讓您開始編寫所喜愛(也沒有錯誤)的多線程程序了。當然,在 JCSP 庫中,這些問題只是冰山之一角。請考慮以下有趣的構造的應用,您也可以自己對它們進行研究: 軟件開發網

    BlackHoleChannel 實現 ChannelOutput 接口,包含一個空的 write() 方法。可以把任意數量的數據寫入這個通道。如果想把現有進程用在不同的環境中,而 再使用某些輸出,那麼 BlackHoleChannel 類是最有用的。因為不能一直留著某個輸出通道不處理(害怕造成死鎖),所以最好的選擇就是讓進程在 BlackHoleChannel 的實例上產生輸出,而 BlackHoleChannel 則有效地充當存放所生成數據的透明無底洞。

    Paraplex 是一個進程,它將其輸入通道集上的多個對象流轉化到單一輸出通道。等到每個輸入通道上都有一些可用輸入之後(在輸入到達的時候就接受,沒有要求或規定特定的順序),就可以將這些輸入打包成數組(數據的尺寸與輸入通道的數量相同),然後把數據通過輸出通道發送為單一輸出。如果需要用表格的形式表現某些數據,那麼 Paraplex 可能會很方便。例如,假設有一個三列的表,每列均用數字填充。前兩列用前面已經介紹過的 JCSP 進程生成的數字填充:NumbersInt 為第一列生成從 0 開始的自然數序列,SquaresInt 為第二列生成對應的平方序列。一個即插即用的叫作 FibonacciInt 的 JCSP 進程(隨 JCSP 庫一道立即可用)可以用來為第三列生成斐波納契數系列。



    可以容易地用一個組合了三個輸入通道的 ParaplexInt 實例來處理這三個列。每個輸入通道都要附著在前面提到過的三個進程中的一個的實例上。然後,ParaplexInt 的單一輸出通道向 CSProcess 傳送數據,後者則接著讀取帶有三個元素的數組,並在適當的格式化表格中輸出它們。

    Deparaplex 是一個類,它和 Paraplex 類相反, 它把數據從單一輸入通道 分離 到一組輸出通道。所以,Deparaplex 類可能讀取一個數組對象(尺寸為 n),把數組中的每個元素逐個輸出到它的 n 個輸出通道上。然後,Deparaplex 進程會一直等候,直到它生成的每個元素都被寫入的通道接受為止。

    請參閱 JCSP 庫的下載 (在 參考資料 中),以獲得這結構造的更多文檔,包括用例。 軟件開發網

    CSP 的好處
    CSP 是基於成熟的數學理論的並發編程范式。因此,CSP 提供了豐富的設計模式和工具集,可以防范常見的多線程缺陷,例如爭用風險、死鎖、活動鎖和資源耗盡。因為所有這些數學上的完善性都構建到了 JCSP 庫中,所以可以直接用它根據規定好的指導原則來編寫應用程序。 (也就是說,不必非得理解理論才能利用它;雖然清楚的了解會更有優勢!)。因為存在正式的針對 Java 的 CSP 模型,所以可以分析並正式地驗證用 JCSP 構造構建的任何多線程 Java 應用程序。

    正如前面所指出的,AOP 的許多好處也適應於基於 CSP 的程序。基於 CSP 的程序的主要好處是關注點的分享。可以使用 CSP 作為程序的基礎(例如通過 JCSP 庫),還可以確保進程之間干淨的解耦,因為它們只能通過通道進行交互,不能通過直接的方法調用進行交互。它還確保可以完美地把數據和應用程序邏輯封裝在通道接口之後的進程之中。所有的副作用都被限制在進程的邊界之內,編程實體之間的交互都是顯式公開的。在基於 JCSP 的程序中,沒有隱藏的互交 —— 在網絡的每一級上,所有的管道都是可見的。

    CSP 的第二個好處是它的復合性質。進程可以組合,從而構成更復雜的進程(復雜的進程還可以再組合起來構成更加復雜的進程),很容易地隨時間推移進行修改和擴展。所以,基於 CSP 的應用程序設計特別簡單並且易於理解,並且在進行維護的時候也非常健壯。CSP 的復合性質也促進了更好的重用;正如在第 2 部分的編程示例中看到的,可以用大量不同的設置使用一個 JCSP 進程,如果需要的話,可以將不希望的輸出重定向到黑洞中。 http://www.mscto.com

    用 CSP 進行並發編程的最後一個好處對於構建分布式系統的開發人員來說特別明顯。在本文中,我描述了實現並發性的不同選項(運行在單一進程中的多線程、運行在一個器上的多進程和運行在多個處理器上的多進程)。通常,這些並發機制中的每一個都要求使用完全不同的執行機制、編程接口和設計范式。而一旦用 CSP 開發應用程序,那麼可以在不影響(至少不非常顯著)應用程序設計或代碼的情況下決定在哪個平台上運行應用程序,比如,是在多線程平台上,還是單一處理器上的多進程平台上,亦或是在分布處理器上運行的多進程平台上。

    JCSP 和 Java.util.concurrent
    大多數 Java 程序員都知道,Java.util.concurrent 包是作為 J2SE 1.5 類庫的標准組成部分引入的。因為JCSP 庫出現的時間比這個包加入 Java 平台的時間早,所以您可能想知道是否需要兩者都用,或者說您想知道,既然已經適應了 Java.util.concurrent,為什麼還要費力地學習 JCSP。

    Java.util.concurrent 包實際上是 Doug Lea 的一個很不錯的流行的 util.concurrent 庫的重新整理。這個包的設計目的是提供一個強壯的、高性能的標准化工具集,簡化 Java 平台上的並發 —— 而這個目的已經實現了!毫無疑問! 如果 Java.util.concurrent 包中的工具適合您的應用程序,那麼請使用它們,您將得到回報。 http://www.mscto.com

    Java 中的通信線程
    JCSP 不是 Java 平台上惟一可用的 CSP 實現。Java 中的通信線程(CTJ) 是另一個流行的 CSP 實現,由 Twente 大學的 Gerald Hilderink 和 Andy Bakkers 開發。請參閱 參考資料,以獲得 JCSP 和 CTJ 的詳細比較,比如說,比較它們的哲學、相似與不同、性能和使用場景。

    http://www.mscto.com

    但是,正如我希望的,本文中的討論已經顯示出:基於 CSP 的技術(通過 JCSP 庫) 表現出的機制超出了 Java.util.concurrent 的范圍。可以把系統構建成通信進程的網絡,並用它們組合成網中之網,實現這一點的范式既自然又簡單。這不僅提高了編寫多線程應用程序的體驗,而且提高了產品 品質。用 JCSP 構建系統會產生干淨的接口,很少有會造成產品不好維護和技術變化這類隱藏的副作用。正式的驗證(即正式地推斷應用程序的安全性和活動屬性)對於安全敏感和高價值的財務應用程序來說也是強大的資產。

    在許多情況下,這兩個包不是互相排斥的:有些 JCSP 的核心要素非常有望會在 Java.util.concurrent 提供的原子的低級機制頂部重新實現,新實現的開支可能要比目前使用的標准監視器機制的開支更少。

    第 3 部分的結束語
    在這篇介紹適用於 Java 程序員的 CSP 的系列文章中,介紹了許多基礎知識。在 第 1 部分 中,我介紹了 Java 語言目前支持的多線程編程的構造。還解釋了這些構造的起源,討論了它們與 Java 平台多線程編程的 4 個常見缺陷(爭用風險、死鎖、活動鎖和資源耗盡)之間的關系。在對第一部分進行總結時,我解釋了為什麼在任意、大型、復雜的多線程應用程序中,很難應用正式的理論消除編程 bug 或證明它們不存在。我推薦使用 CSP 作為這個困境的備選項。 http://www.mscto.com

    在 第 2 部分 中,我介紹了 CSP 理論和它最流行的一個基於 Java 的實現 —— JCSP 庫。我用了幾個實例演示了基本的 JCSP 構造( 例如進程、通道和警衛等)的使用。

    在最後這篇文章中,我介紹了 JCSP 中的高級主題,其中包括它與面向方面編程的相似性,以及它與 Java 平台的其他一些並發編程包的比較。我還演示了一些用來解決更復雜的同步問題的 JCSP 構造,簡要討論了用 JCSP.Net 包進行分布式編程的可能性。

    我希望我已經表達得夠清楚,用 JCSP 編寫多線程 Java 應用程序有許多優勢。下次您會發現,在您自己考慮(也可能要在躲避)多線程應用程序設計時,您可能想轉而采用 JCSP 庫。JCSP 提供了非常簡化的並發編程方法,生成的程序既能對最常見的多線程應用程序開發缺陷進行驗證,還易於理解、調試和長遠維護它們。

    致謝
    非常感謝 Peter Welch 教授在我編寫這個文章系列期間給予的鼓勵。他在百忙之中抽出時間非常細致地審閱了草稿,並提供了許多寶貴的提高此系列文章的質量和准確性的建議。如果文章中還存在錯誤,那都是由於我的原因!我在文章中使用的示例基於或來自 JCSP 庫的 Javadoc 中提供的示例,以及 JCSP Web 站點上提供的 PowerPoint 演示文稿。這兩個來源提供了需要探索的大量信息。

    參考資料 您可以參閱本文在 developerWorks 全球站點上的 英文原文。

    Nicholas LesIEcki 撰寫的“使用面向 ASPect 的編程改進模塊性” (developerWorks,2002 年 1 月)第一次介紹了 AOP 的好處。

    請參閱 developerWorks 上為期一年的 AOP@Work 系列,並從其中突出強調的部分學習更多關於方面編程的內容。

    並發性專家 Brian Goetz 在“Java 理論與實踐: JDK 5.0 中更靈活、更具可伸縮性的鎖定機制”(developerWorks,2004 年 10 月)這篇文章中解釋了 Java.util.concurrent 的一些優勢與不足。

    Goetz 在“Java 理論與實踐: 流行的原子” (developerWorks,2004 年 11 月)這篇文章中解釋了 Java.util.concurrent 中原子類的重要性。

    C.A.R. Hoare 撰寫的“Communicating Sequential Processes”把通信順序進程的並行復合作為一個基本的編程結構化方法引入進來介紹(Communications of the ACM Archive,1978)。

    可以免費獲得 PDF 格式的 C.A.R. Hoare 撰寫的 關於 CSP 的書籍。

    Bill Roscoe 撰寫的 Theory and Practice of Concurrency (Prentice Hall, 1997) 是關於並發性和 CS 主題的最新書籍。

    牛津大學計算機實驗室負責的 CSP Archive 是學習更多關於 CSP 內容的好地方,除此之外,還有 WoTUG homepage。

    JCSP home page 由英國坎特伯雷市肯特大學負責。

    可以從 QuickStone 科技獲得 Network Edition of JCSP。

    FDR2 (故障偏差求精,Failures-Divergence Refinement)是面向基於 CSP 的程序的幾個商業化模型檢測工具之一。

    Gerald Hilderink、Jan Broenink 和 Andre Bakkers 在“Communicating Threads for Java”(iOS Press,1999)這篇論文中描述了 JCSP 庫的一個替代品。

    Peter Welch 教授和其他人合著的論文“Using Java for Parallel Computing -- JCSP versus CTJ” (iOS Press, 2000)比較了 JCSP 和 CTJ。
    CSP 可以使用 Java 語言實現,也可以使用其他語言實現: C CSP 是針對 C 的實現,而 J#.Net 是針對 .Net 的實現。

    Occam-pi 是一個語言平台,它打算用 pi-calculus 的移動特性來擴展 occam 語言的 CSP 想法。請從 occam-pi homepage 了解這個前沿研究的更多信息。

    在學習 occam 時,您可能還想調查 occam 編程器的各種擴展。

    在 developerWorks Java 技術專區 中可以找到 Java 編程各方面的文章。

    請參閱 Developer Bookstore,以獲得技術書籍的完整清單,其中包括數百本 Java 相關主題的書籍。

    還請參閱 Java 技術專區教程頁,以獲得 developerWorks 上免費的、以 Java 為重點的教程。

    關於作者
    Abhijit Belapurkar 擁有位於印度德裡市的印度理工學院(IIT)計算機科學的理工學士學位。在過去的 11 年中,他一直工作在分布式應用程序的架構和信息安全領域,他在使用 Java 平台構建 n 層應用程序方面大約有 6 年的工作經驗。他目前是作為高級技術架構師在 J2EE 領域工作,服務於印度班加羅爾的 Infosys 科技有限公司。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved