程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> Java開發2.0: Kilim簡介

Java開發2.0: Kilim簡介

編輯:關於JAVA

一種用於實現 Java 並發性的角色框架

簡介:並發編程是 Java™ 開發 2.0 的核心概念,但可能不是基於線程的並發性。Andrew Glover 解釋為什麼在多核系統中進行並發編程時,角色優於線程。他然後介紹 Kilim —— 一種基於角 色的消息傳遞框架,結合了並發編程和分布式編程。

對於軟件開發人員而言,調試多線程應用程序中的非確定缺陷是最痛苦的工作。因此,像大多數人一 樣,我鐘愛使用 Erlang 和 Scala 等函數語言進行並發編程。

Scala 和 Erlang 都采用了角色模型來進行並發編程,沒有采用線程概念。圍繞角色模型的創新並不 僅限於語言本身,角色模型也可供 Kilim 等基於 Java 的角色框架使用。

Kilim 對角色模型的使用非常直觀,稍後您將看到,該庫使構建並發應用程序變得異常簡單。

多核挑戰

在 2005 年,Herb Sutter 編寫了一篇現在仍然頗為著名的文章 “The Free Lunch is Over: A Fundamental Turn Toward Concurrency in Software”。在這篇文章中,他摒棄了一直誤導著人們的觀 念,那就是摩爾定律將繼續促進越來越高的 CPU 時鐘速率。

Sutter 預言了 “免費午餐” 的終結,通過越來越快的芯片來捎帶提升軟件應用程序的性能將不再可 能。相反,他認為應用程序性能的顯著提升將需要利用多核芯片架構來實現。

事實證明他是對的。芯片制造商已經達到了一種硬性限制,芯片速率已穩定在 3.5 GHz 左右多年了。 隨著制造商越來越快地增加芯片上的核心數量,摩爾定律在多核領域繼續得以滿足。

Sutter 還提到並發編程將使開發人員能夠利用多核架構。但是,他補充道,“相比如今的各種語言提 供的編程模型,我們亟需一種更高級的並發編程模型。”

Java 等語言的基本編程模型是基於線程的。盡管多線程應用程序並不是很難編寫,但正確 編寫它們 仍然面臨許多挑戰。並發編程的一個困難之處是利用線程來考慮並發性。如今已有許多並發模型,一種特 別有趣並獲得了 Java 社區認可的模型就是角色模型。

角色模型

角色模型是一種不同的並發進程建模方式。與通過共享內存與鎖交互的線程不同,角色模型利用了 “ 角色” 概念,使用郵箱來傳遞異步消息。在這裡,郵箱 類似於實際生活中的郵箱,消息可以存儲並供其 他角色檢索,以便處理。郵箱有效地將各個進程彼此分開,而不用共享內存中的變量。

角色充當著獨立且完全不同的實體,不會共享內存來進行通信。實際上,角色僅能通過郵箱通信。角 色模型中沒有鎖和同步塊,所以不會出現由它們引發的問題,比如死鎖、嚴重的丟失更新問題。而且,角 色能夠並發工作,而不是采用某種順序方式。因此,角色更加安全(不需要鎖和同步),角色模型本身能 夠處理協調問題。在本質上,角色模型使並發編程更加簡單了。

角色模型並不是一個新概念,它已經存在很長時間了。一些語言(比如 Erlang 和 Scala)的並發模 型就是基於角色的,而不是基於線程。實際上,Erlang 在企業環境中的成功(Erlang 由 Ericsson 創建 ,在電信領域有著悠久的歷史)無疑使角色模型變得更加流行,曝光率更高,而且這也使它成為了其他語 言的一種可行的選擇。Erlang 是角色模型更安全的並發編程方法的一個傑出示例。

不幸的是,角色模型並沒有植入到 Java 平台中,但我們可以通過各種方式使用它。JVM 對替代語言 的開放性意味著您可以通過 Java 平台語言(比如 Scala 或 Groovy)來利用角色。另外,您可以試用一 種支持角色模型且基於 Java 的庫,比如 Kilim。

Kilim 中的角色

Kilim 是一個使用 Java 編寫的庫,融入了角色模型的概念。在 Kilim 中,“角色” 是使用 Kilim 的 Task 類型來表示的。Task 是輕量型的線程,它們通過 Kilim 的 Mailbox 類型與其他 Task 通信。

Mailbox 可以接受任何類型的 “消息”。例如,Mailbox 類型接受 java.lang.Object。Task 可以發 送 String 消息或者甚至自定義的消息類型,這完全取決於您自己。

在 Kilim 中,所有實體都通過方法簽名捆綁在一起,如果您需要同時執行幾項操作,可以在一個方法 中指定該行為,擴大該方法的簽名以拋出 Pausable。因此,在 Kilim 中創建並發類就像在 Java 中實現 Runnable 或擴展 Thread 一樣簡單。只是使用 Runnable 或 Thread 的附加實體(比如關鍵字 synchronized)更少了。

最後,Kilim 的魔力是由一個稱為 weaver 的後期進程來實現的,該進程轉換類的字節碼。包含 Pausablethrows 字句的方法在運行時由一個調度程序處理,該調度程序包含在 Kilim 庫中。該調度程序 處理有限數量的內核線程。可以利用此工具來處理更多的輕量型線程,這可以最大限度地提高上下文切換 和啟動的速度。每個線程的堆棧都是自動管理的。

在本質上,Kilim 使創建並發進程變得輕松而簡單:只需從 Kilim 的 Task 類型進行擴展並實現 execute 方法。編譯新創建的支持並發性的類之後,對其運行 Kilim 的 weaver,您會實現顯著的性能提 升!

Kilim 最初是一種外來語言,但它帶來了巨大的回報。角色模型(以及後來的 Kilim)使編寫依賴於 類似對象的異步操作對象變得更加簡單和安全。您可以 使用 Java 的基本線程模型進行同樣的操作(比 如擴展 Thread),但這更具挑戰性,因為它會將您帶回鎖和同步的世界中。簡而言之,將您的並發編程 模型轉換為角色使多線程應用程序更容易編碼。

Kilim 實戰

在 Kilim 的角色模型中,消息通過 Mailbox 在進程之間傳送。在許多情況下,您可以將 Mailbox 看 作隊列。進程可以將一些項加入郵箱中,也可以從郵箱獲取一些項,而且它們既可以采用阻塞方式,也可 以采用非阻塞方式來這樣做(阻塞對象是底層 Kilim 實現的輕量型進程,而不是內核線程)。

作為在 Kilim 中利用郵箱的一個示例,我編寫了兩個角色(Calculator 和 DeferredDivision),它 們從 Kilim 的 Task 類型擴展而來。這些類將以一種並發方式協同工作。DeferredDivision 對象將創建 一個被除數和一個除數,但它不會嘗試將這兩個數相除。我們知道除法運算很耗資源,所以 DeferredDivision 對象將要求 Calculator 類型來處理該任務。

這兩個角色通過一個共享 Mailbox 實例通信,該實例接受一個 Calculation 類型。這種消息類型非 常簡單 —— 已提供了被除數和除數,Calculator 隨後將執行計算並設定相應的答案。Calculator 然後 將這個 Calculation 實例放回共享 Mailbox 中。

Calculation

清單 1 給出了這個簡單的 Calculation 類型。您會注意到這個類型不需要任何特殊的 Kilim 代碼。 實際上,它只是一個再普通不過的 Java bean。

清單 1. 一個 Calculation 類型的消息

import java.math.BigDecimal;

public class Calculation {
  private BigDecimal dividend;
  private BigDecimal divisor;
  private BigDecimal answer;

  public Calculation(BigDecimal dividend, BigDecimal divisor) {
  super();
  this.dividend = dividend;
  this.divisor = divisor;
  }

  public BigDecimal getDividend() {
  return dividend;
  }

  public BigDecimal getDivisor() {
  return divisor;
  }

  public void setAnswer(BigDecimal ans){
  this.answer = ans;
  }

  public BigDecimal getAnswer(){
  return answer;
  }

  public String printAnswer() {
  return "The answer of " + dividend + " divided by " + divisor +
   " is " + answer;
  }
}

DeferredDivision

DeferredDivision 類中使用了特定於 Kilim 的類。該類執行多項操作,但總體來講它的工作非常簡 單:使用隨機數(類型為 BigDecimal)創建 Calculation 的實例,將它們發送到 Calculator 角色。而 且,該類還會檢查共享的 MailBox,以查看其中是否有任何 Calculation。如果檢索到的一個 Calculation 實例有一個答案,DeferredDivision 將打印它。

清單 2. DeferredDivision 創建隨機除數和被除數

import java.math.BigDecimal;
import java.math.MathContext;
import java.util.Date;
import java.util.Random;

import kilim.Mailbox;
import kilim.Pausable;
import kilim.Task;

public class DeferredDivision extends Task {

  private Mailbox<Calculation> mailbox;

  public DeferredDivision(Mailbox<Calculation> mailbox) {
  super();
  this.mailbox = mailbox;
  }

  @Override
  public void execute() throws Pausable, Exception {
  Random numberGenerator = new Random(new Date().getTime());
  MathContext context = new MathContext(8);
  while (true) {
   System.out.println("I need to know the answer of something");
   mailbox.putnb(new Calculation( 
    new BigDecimal(numberGenerator.nextDouble(), context),
    new BigDecimal(numberGenerator.nextDouble(), context)));
   Task.sleep(1000);
   Calculation answer = mailbox.getnb(); // no block 
   if (answer != null && answer.getAnswer() != null) {
   System.out.println("Answer is: " + answer.printAnswer());
   }
  }
  }
}

從清單 2 可以看到,DeferredDivision 類擴展了 Kilim 的 Task 類型,後者實際上模仿了角色模型 。注意,該類還改寫了 Task 的 execute 方法,後者默認情況下拋出 Pausable。因此,execute 的操作 將在 Kilim 的調度程序控制下進行。也就是說,Kilim 將確保 execute 以一種安全的方式並行地運行。

在 execute 方法內部,DeferredDivision 創建 Calculation 的實例並將它們放在 Mailbox 中。它 使用 putnb 方法以一種非阻塞方式完成此任務。

填充 mailbox 後,DeferredDivision 進入休眠狀態 —— 注意,與處於休眠狀態的內核線程不同, 它是由 Kilim 托管的輕量型線程。當角色喚醒之後,像前面提到的一樣,它在 mailbox 中查找任何 Calculation。此調用也是非阻塞的,這意味著 getnb 可以返回 null。如果 DeferredDivision 找到一 個 Calculation 實例,並且該實例的 getAnswer 方法有一個值(也就是說,不是一個已由 Calculator 類型處理過的 Calculation 實例),它將該值打印到控制台。

Calculator

Mailbox 的另一端是 Calculator。與清單 2 中定義的 DeferredDivision 角色類似,Calculator 也 擴展了 Kilim 的 Task 並實現了 execute 方法。一定要注意兩個角色都共享同一個 Mailbox 實例。它 們不能與不同的 Mailbox 通信,它們需要共享一個實例。相應地,兩個角色都通過它們的構造函數接受 一個有類型 Mailbox。

清單 3. 最終的實際運算角色:Calculator

import java.math.RoundingMode;

import kilim.Mailbox;
import kilim.Pausable;
import kilim.Task;

public class Calculator extends Task{

  private Mailbox<Calculation> mailbox;

  public Calculator(Mailbox<Calculation> mailbox) {
  super();
  this.mailbox = mailbox;
  }

  @Override
  public void execute() throws Pausable, Exception {
  while (true) {
   Calculation calc = mailbox.get(); // blocks 
   if (calc.getAnswer() == null) {
   calc.setAnswer(calc.getDividend().divide(calc.getDivisor(), 8,
    RoundingMode.HALF_UP));
   System.out.println("Calculator determined answer");
   mailbox.putnb(calc);
   }
   Task.sleep(1000);
  }
  }
}

Calculator 的 execute 方法與 DeferredDivision 的相應方法一樣,不斷循環查找共享 Mailbox 中 的項。區別在於 Calculator 調用 get 方法,這是一種阻塞調用。相應地,當一條 Calculation “消息 ” 顯示時,它執行請求的除法運算。最後,Calculator 將修改的 Calculation 放回到 Mailbox 中(采 用非阻塞方式),然後進入休眠狀態。兩個角色中的休眠調用都僅用於簡化控制台的讀取。

Kilim 的 weaver

在前面,我提到了 Kilim 通過其 weaver 執行字節碼操作。這是一個簡單的後處理過程,您在編譯了 類之後 運行它。weaver 然後將一些特殊代碼添加到包含 Pausable 標記的各種類和方法中。

調用 weaver 非常簡單。舉例而言,在清單 4 中,我使用 Ant 調用 Weaver。我需要做的只是告訴 Weaver 我需要的類在哪裡,以及在哪裡放置生成的字節碼。在這個例子中,我讓 Weaver 更改 target/classes 字典中的類,並將生成的字節碼寫回到該字典。

清單 4. Ant 調用 Kilim 的 weaver

<target name="weave" depends="compile" description="handles Kilim byte  code weaving">
  <java classname="kilim.tools.Weaver" fork="yes">
  <classpath refid="classpath" />
  <arg value="-d" />
  <arg value="./target/classes" />
  <arg line="./target/classes" />
  </java>
</target> 

更改代碼之後,我就可以在運行時隨意利用 Kilim 了,只要我在類路徑中包含了它的 .jar 文件。

在運行時使用 Kilim

將這兩個角色應用到實際中就像在 Java 代碼中應用兩個普通的 Thread 一樣。您使用同一個共享 sharedMailbox 實例創建並擴展兩個角色實例,然後調用 start 方法來實際設置它們。

清單 5. 一個簡單的程序

import kilim.Mailbox;
import kilim.Task;

public class CalculationCooperation {
  public static void main(String[] args) {
  Mailbox<Calculation> sharedMailbox = new Mailbox<Calculation> ();

  Task deferred = new DeferredDivision(sharedMailbox);
  Task calculator = new Calculator(sharedMailbox);

  deffered.start();
  calculator.start();

  }
}

運行這兩個角色會得到如清單 6 所示的輸出。如果運行此代碼,您的輸出可能有所不同,但活動的邏 輯順序將保持不變。在清單 6 中,DeferredDivision 請求計算,Calculator 使用一個答案作為響應。

清單 6. 您的輸出將有所不同 —— 各個角色不是一成不變的

[java] I need to know the answer of something
[java] Calculator determined answer
[java] Answer is: The answer of 0.36477377 divided by 0.96829189 is  0.37671881
[java] I need to know the answer of something
[java] Calculator determined answer
[java] Answer is: The answer of 0.40326269 divided by 0.38055487 is  1.05967029
[java] I need to know the answer of something
[java] Calculator determined answer
[java] Answer is: The answer of 0.16258913 divided by 0.91854403 is  0.17700744
[java] I need to know the answer of something
[java] Calculator determined answer
[java] Answer is: The answer of 0.77380722 divided by 0.49075363 is  1.57677330

結束語

角色模型支持采用一種更安全的機制來在進程(或角色)之間進行消息傳遞,極大地方便了並發編程 。此模型的實現因語言和框架的不同而不同。我建議參考 Erlang 的角色,其次是 Scala 的角色。兩種 實現都很簡潔,都具有各自的語法。

如果您想要利用 “plain Jane” Java 角色,那麼您最好的選擇可能是 Kilim 或一種類似框架。世 上沒有免費的午餐,但基於角色的框架確實使並發編程以及利用多核進程變得更加簡單。

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