Java 开发 2.0: Kilim 简介
2016-12-29 19:24
309 查看
对于软件开发人员而言,调试多线程应用程序中的非确定缺陷是最痛苦的工作。因此,像大多数人一样,我钟爱使用 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 左右多年了。随着制造商越来越快地增加芯片上的核心数量,摩尔定律在多核领域继续得以满足。
自 Java 技术首次诞生以来,Java 开发格局已发生了翻天覆地的变化。得益于成熟的开源框架和可靠的租赁部署基础设施,现在可以迅速而经济地组装、测试、运行和维护
Java 应用程序。在本 系列 中,Andrew Glover 探索使这种新的 Java 开发风格成为可能的各种技术和工具。
Sutter 还提到并发编程将使开发人员能够利用多核架构。但是,他补充道,“相比如今的各种语言提供的编程模型,我们亟需一种更高级的并发编程模型。”
Java 等语言的基本编程模型是基于线程的。尽管多线程应用程序并不是很难编写,但正确 编写它们仍然面临许多挑战。并发编程的一个困难之处是利用线程来考虑并发性。如今已有许多并发模型,一种特别有趣并获得了 Java 社区认可的模型就是角色模型。
角色模型
角色模型是一种不同的并发进程建模方式。与通过共享内存与锁交互的线程不同,角色模型利用了 “角色” 概念,使用邮箱来传递异步消息。在这里,邮箱类似于实际生活中的邮箱,消息可以存储并供其他角色检索,以便处理。邮箱有效地将各个进程彼此分开,而不用共享内存中的变量。
角色充当着独立且完全不同的实体,不会共享内存来进行通信。实际上,角色仅能通过邮箱通信。角色模型中没有锁和同步块,所以不会出现由它们引发的问题,比如死锁、严重的丢失更新问题。而且,角色能够并发工作,而不是采用某种顺序方式。因此,角色更加安全(不需要锁和同步),角色模型本身能够处理协调问题。在本质上,角色模型使并发编程更加简单了。
角色模型并不是一个新概念,它已经存在很长时间了。一些语言(比如 Erlang 和 Scala)的并发模型就是基于角色的,而不是基于线程。实际上,Erlang 在企业环境中的成功(Erlang 由 Ericsson 创建,在电信领域有着悠久的历史)无疑使角色模型变得更加流行,曝光率更高,而且这也使它成为了其他语言的一种可行的选择。Erlang 是角色模型更安全的并发编程方法的一个杰出示例。
不幸的是,角色模型并没有植入到 Java 平台中,但我们可以通过各种方式使用它。JVM 对替代语言的开放性意味着您可以通过 Java 平台语言(比如 Scala 或 Groovy)来利用角色(参见 参考资料,了解
Groovy 的角色库 GPars)。另外,您可以试用一种支持角色模型且基于 Java 的库,比如 Kilim。
Kilim中的角色
Kilim 是一个使用 Java 编写的库,融入了角色模型的概念。在 Kilim 中,“角色” 是使用 Kilim 的
在 Kilim 中,所有实体都通过方法签名捆绑在一起,如果您需要同时执行几项操作,可以在一个方法中指定该行为,扩大该方法的签名以抛出
最后,Kilim 的魔力是由一个称为 weaver 的后期进程来实现的,该进程转换类的字节码。包含
在本质上,Kilim 使创建并发进程变得轻松而简单:只需从 Kilim 的
Kilim 最初是一种外来语言,但它带来了巨大的回报。角色模型(以及后来的 Kilim)使编写依赖于类似对象的异步操作对象变得更加简单和安全。您可以使用 Java 的基本线程模型进行同样的操作(比如扩展
Kilim实战
在 Kilim 的角色模型中,消息通过
作为在 Kilim 中利用邮箱的一个示例,我编写了两个角色(
这两个角色通过一个共享
Calculation
清单 1 给出了这个简单的
清单 1. 一个 Calculation 类型的消息
DeferredDivision
清单 2. DeferredDivision 创建随机除数和被除数
从清单 2 可以看到,
在
填充
Calculator
清单 3. 最终的实际运算角色:Calculator
Kilim 的 weaver
在前面,我提到了 Kilim 通过其 weaver 执行字节码操作。这是一个简单的后处理过程,您在编译了类之后 运行它。weaver 然后将一些特殊代码添加到包含
调用 weaver 非常简单。举例而言,在清单 4 中,我使用 Ant 调用 Weaver。我需要做的只是告诉 Weaver 我需要的类在哪里,以及在哪里放置生成的字节码。在这个例子中,我让 Weaver 更改
清单 4. Ant 调用 Kilim 的 weaver
更改代码之后,我就可以在运行时随意利用 Kilim 了,只要我在类路径中包含了它的 .jar 文件。
在运行时使用Kilim
将这两个角色应用到实际中就像在 Java 代码中应用两个普通的
清单 5. 一个简单的程序
运行这两个角色会得到如清单 6 所示的输出。如果运行此代码,您的输出可能有所不同,但活动的逻辑顺序将保持不变。在清单 6 中,
清单 6. 您的输出将有所不同 —— 各个角色不是一成不变的
结束语
角色模型支持采用一种更安全的机制来在进程(或角色)之间进行消息传递,极大地方便了并发编程。此模型的实现因语言和框架的不同而不同。我建议参考 Erlang 的角色,其次是 Scala 的角色。两种实现都很简洁,都具有各自的语法。
如果您想要利用 “plain Jane” Java 角色,那么您最好的选择可能是 Kilim 或一种类似框架(参见 参考资料)。世上没有免费的午餐,但基于角色的框架确实使并发编程以及利用多核进程变得更加简单。
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 左右多年了。随着制造商越来越快地增加芯片上的核心数量,摩尔定律在多核领域继续得以满足。
Java 应用程序。在本 系列 中,Andrew Glover 探索使这种新的 Java 开发风格成为可能的各种技术和工具。
Sutter 还提到并发编程将使开发人员能够利用多核架构。但是,他补充道,“相比如今的各种语言提供的编程模型,我们亟需一种更高级的并发编程模型。”
Java 等语言的基本编程模型是基于线程的。尽管多线程应用程序并不是很难编写,但正确 编写它们仍然面临许多挑战。并发编程的一个困难之处是利用线程来考虑并发性。如今已有许多并发模型,一种特别有趣并获得了 Java 社区认可的模型就是角色模型。
角色模型
角色模型是一种不同的并发进程建模方式。与通过共享内存与锁交互的线程不同,角色模型利用了 “角色” 概念,使用邮箱来传递异步消息。在这里,邮箱类似于实际生活中的邮箱,消息可以存储并供其他角色检索,以便处理。邮箱有效地将各个进程彼此分开,而不用共享内存中的变量。
角色充当着独立且完全不同的实体,不会共享内存来进行通信。实际上,角色仅能通过邮箱通信。角色模型中没有锁和同步块,所以不会出现由它们引发的问题,比如死锁、严重的丢失更新问题。而且,角色能够并发工作,而不是采用某种顺序方式。因此,角色更加安全(不需要锁和同步),角色模型本身能够处理协调问题。在本质上,角色模型使并发编程更加简单了。
角色模型并不是一个新概念,它已经存在很长时间了。一些语言(比如 Erlang 和 Scala)的并发模型就是基于角色的,而不是基于线程。实际上,Erlang 在企业环境中的成功(Erlang 由 Ericsson 创建,在电信领域有着悠久的历史)无疑使角色模型变得更加流行,曝光率更高,而且这也使它成为了其他语言的一种可行的选择。Erlang 是角色模型更安全的并发编程方法的一个杰出示例。
不幸的是,角色模型并没有植入到 Java 平台中,但我们可以通过各种方式使用它。JVM 对替代语言的开放性意味着您可以通过 Java 平台语言(比如 Scala 或 Groovy)来利用角色(参见 参考资料,了解
Groovy 的角色库 GPars)。另外,您可以试用一种支持角色模型且基于 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 的后期进程来实现的,该进程转换类的字节码。包含
Pausable
throws字句的方法在运行时由一个调度程序处理,该调度程序包含在 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类中使用了特定于 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()); } } } } |
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
将这两个角色应用到实际中就像在 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); deferred.start(); calculator.start(); } } |
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 或一种类似框架(参见 参考资料)。世上没有免费的午餐,但基于角色的框架确实使并发编程以及利用多核进程变得更加简单。
相关文章推荐
- Java 开发 2.0: Kilim 简介
- Java 开发 2.0: Kilim 简介
- Java 开发 2.0: Kilim 简介
- Java 开发 2.0: Kilim 简介
- Java.util包简介并教您如何开发应用之二 (2)
- XMLBEANS 2.0 —— 一位JAVA开发人员的视点
- ASP.NET 2.0程序开发详解 -----------15.1 AJAX简介
- Java EE WEB工程师培训—JDBC+Servlet+JSP整合开发之32. HTML简介 推荐
- Java Web2.0项目开发进阶宝典即将上市
- Java RPC通信机制之XML-RPC:Apache XML-RPC 3.0开发简介
- Java.util包简介并教您如何开发应用之二-Java基础-Java-编程开发
- Java 开发 2.0: 使用 Google App Engine
- Java.util包简介并教您如何开发应用之二 (3)
- XMLBEANS 2.0 —— 一位JAVA开发人员的视点
- Java RPC通信机制之XML-RPC:Apache XML-RPC 3.0开发简介
- Struts2.0 (开发之一)--简介与简单登录的实现--CTO
- Java.util包简介并教您如何开发应用之二 (1)
- Maven 应用小简介 (注:maven2是指在maven1后重新开发的maven,如同java与java2)
- Java GUI/界面 开发 简介
- Java开发三剑客JSF2.0、EJB3.1、JPA2.0现状及发展