转自:http://www.ibm.com/developerworks/cn/java/j-rmiframe/#code_listing_5
许多 Java 开发者都误认为他们可以现成的将远程方法调用(Remote Method Invocation(RMI))服务器用作成熟的应用程序服务器。这是错误的假定,并随着开发的进展,它将导自极大的痛苦。一个比较好的方法是建立一个 围绕 RMI 的框架,由该框架为这样的成熟的应用程序服务器提供结构。
高容量的、可恢复的、安全的、可管理的软件系统的基本组件(最初与事务处理服务器一起引入)是异步(或后端)处理。异步处理的基本流有如下步骤:
隶属于异步处理的基本构件是队列和线程结构。把队列和线程加到 RMI 服务器(一个异步的实体),然后您将得到一个有任务能力的服务器。最好的部分是那并不是很难做到的。
提到线程,大多数开发者都会畏缩;但处理线程是创建我们所要的这种有任务能力的服务器的核心。有太多的关于线程的书和文章,以至于任何 明智的人都会对多线程项目感到害怕。但事实上单功能的线程是容易设计的。处理应用程序的多线程的要点是将线程从应用程序分离,并单独的控制每个线程。本文 将为您演示如何去做。
RMI 服务器在计算机中作为独立的进程。由于该进程和客户机进程可以彼此独立的运行,所以交互是异步的。异步的进程需要能使其自恃的所需的某一程度的管理 ― 框架。
本文帮助您理解为什么异步进程需要管理,并概述了最后设计自己的定制的异步进程管理器的必要步骤。
首先,我们来研究单组件异步进程管理器的队列和线程环境。然后,我们将此单组件环境转换为可以并行处理多个队列的请求代理。
您应该稍微熟悉一下 RMI,至少是已经读了一些在 参考资料 部分提到的 RMI 教程。这是个困难的题目,所以我们一次只做一小步。有时您需要看一下演示来掌握要点;本文中我们有一个演示。而且有时您需要查阅代码来真正理解正在做什么;我们也提供了代码。自然的,您应该熟悉同步处理和异步处理的不同。
为什么 EJB 和 GUI 应用程序会如此成功?因为它们在容器 ― 框架 (用于管理持久性、消息传递、线程管理、日志记录、事件队列、用户界面等等,还有很多)内运行。
RMI 服务器没有相似的应用程序容器来管理队列、线程、消息传递、日志记录等等。我们必须自己建立它。
RMI 运行时是 Java 设计师的一个伟大创作。对于大多数实现来说,RMI 运行时实例化一个线程来处理来自客户机的每个请求。一旦请求完成了,线程为下一个客户机请求等待短暂的时间。通过这种方法,RMI 连接可以重用线程。如果没有新的请求进来,RMI 运行时就销毁这个线程。
开发者有时会忘记 RMI 运行时不是而且也从不应该是成熟的应用程序服务器。使用基本的无应用程序服务的 RMI 运行时会使开发变难。考虑这两个非常基础的问题:
对这些问题和更多的问题的实用的解决方案是从应用程序处理中分离 RMI 连接活动。您可以通过在服务器端创建一个应用程序队列和线程结构来做到这一点。这种是高可靠的、完全任务重要的软件产品的工作方式,而且该结构适用于任何应用程序。
想象适用该模型。对于需要已计时响应的客户机,RMI 连接线程与应用程序线程联系。如果应用程序线程没有在时间限制内响应,那么 RMI 连接线程向客户机返回一个超时消息。对于自主的请求,RMI 连接线程联系应用程序线程,并立即返回客户机一个“已经被调度”的消息。
现在,目标是设计一个队列和应用程序线程环境以便:
我们将研究您可以运行的异步进程管理器框架。您可以从 参考资料 部分下载这些类来执行,也可以下载到源代码。
与任何软件系统一样,框架是由基础的构件组成的。整个结构最初看起来也许很复杂,但它其实不过是建立在彼此之上的组件的共同工作罢了。在接下来的几节中,我们将讨论框架的五个不可缺的元素。
然后,我们用框架来大致处理一个来自客户机的简单的请求。
操作系统把 Java 虚拟机看作 进程 ,把线程看作 轻量级进程 。我们正研究的异步进程管理器的底层模型被称为 逻辑进程 。该模型看起来象什么呢?
考虑一个逻辑进程如在 Amazon.com 完成订单活动。您上线并下定单。由于 Amazon 没有专门针对您的收定单的人,所以您的定单进入定单队列。您继续您的业务。在 Amazon 仓库,某个人从先进先出(FIFO)定单队列拿到您的定单并填充定单。如果您想要一本书,也许定单就是由一个特定仓库的一些可以填写定单的雇员填写的。如 果您想要一个盒式录像带,也许定单是由另一个不同的仓库的一些不同的雇员填写的,根据要求仓库也许更大,也许更小。这个定单完成流程是个逻辑进程。
软件中的被管理的进程环境行为相似。在 Amazon,填写定单的是雇员。在软件系统中,填写定单的是应用程序线程。
对后端服务的请求在队列中排队等待。每个 RMI 连接线程把客户机的请求放进队列。应用程序线程从队列中取得请求,并对其做出反应。开发者为不同类型的请求定义队列(象在 Amazon 将放书的仓库和放盒式录像带的仓库分离),并且定义了为每个队列服务的应用程序线程的最大数目。这些是逻辑进程。
概述异步进程管理器如何使用逻辑进程
客户机向服务器发送请求。RMI 运行时创建或重用一个 RMI 连接线程,该线程执行实现类中的一个方法。恰当的方法会把请求放进队列中的优先等待列表,并且使用对象通知方法唤醒应用程序线程来为请求服务。这里有两种可能的请求:
来看一下异步进程管理器的不同部分,它们使事情更真切。关键组件是我们需要使用 RMI 的基本的东西和我们需要实现队列/线程模型的特定于类的应用程序。
基本的 RMI 要素
RMI 要求我们建立如下的东西:
java.rmi.Remote
接口 对于该框架,远程接口(如清单 1 所示)需要至少三个方法:
syncRequest()
方法 asyncRequest()
方法 shutDown()
public interface FrameWorkInterface extends Remote { public Object[] syncRequest(FrameWorkParm in) throws RemoteException; public Object[] asyncRequest(FrameWorkParm in) throws RemoteException; public String shutRequest() throws RemoteException; |
具体的实现类 FrameWorkInterface
的实现是 FrameWorkImpl
类。清单 2 是实例域和一部分构造器。因为这是 RMI 连接线程逻辑所在的类(把请求放进请求队列,唤醒一个应用程序线程,将回应返回客户机),所以我们将在本文稍后的地方讨论内部的运作。
public final class FrameWorkImpl extends UnicastRemoteObject implements FrameWorkInterface { // instance field (base of common memory) private FrameWorkBase fwb; // constructor public FrameWorkImpl (FrameWorkBase reference_to_fwb) throws RemoteException { // set common memory reference fwb = reference_to_fwb; |
参数类 这个类(如清单 3 所示)将客户机信息传递给 RMI 服务器。实例域如下:
Object client_data
:从客户机到应用程序处理类的可选对象。 String func_name
:逻辑进程的名称;在本文的后面部分也将其称为 函数 function 。 int wait_time
:当使用 syncRequest()
时,RMI 连接线程等待应用程序线程完成的最大的秒数(即,超时间隔)。 int priority
:请求的优先级。优先级为 1 的请求应该在优先级为 2 的请求前被选择,优先级为 2 的请求应该在优先级为 3 的请求前被选择,诸如此类。 public final class FrameWorkParm implements java.io.Serializable { // passed data from Client private Object client_data; // Function name private String func_name; // maximum time to wait for a reply private int wait_time; // priority of the request private int priority; |
特定于应用程序的要素
除了为 RMI 而需要的外,应用程序还要求我们建立如下的东西:
启动类 该类,如清单 4 所示,启动 RMI 服务器。它包含用于建立持久环境的逻辑。
public final class FrameWorkServer { // The base for all persistent processing private static FrameWorkBase fwb = null; // Start up Server public static void main(java.lang.String[] args){ // the base for all processing fwb = new FrameWorkBase(); // now, after initializing the other FrameWorkBase fields // including the application queues and threads, // do the Implementation class with a ref to FrameWorkBase FrameWorkImpl fwi = new FrameWorkImpl(fwb); |
应用程序队列 应用程序队列包含三个主要元素:
对于该分离最初也许有点疑惑,因为大多数开发者将线程看作应用程序类的一部分。处理除应用程序逻辑之外的线程逻辑需要将两种思维方式合并起来。该框架设计将线程逻辑从应用程序逻辑分离(我们称为 应用程序处理类 ),从而应用程序逻辑能够容易的插入线程结构。(参见清单 5。)
仍然困惑?考虑您用来阅读本文的浏览器。您愿意放首歌或看个录像吗?想想必须把那个逻辑放进基础产品的可怜的浏览器的开发者吧。但如果该增加的逻辑是个插件,那么不仅基础代码小并容易维护,而且您还可以在任何时间安装任何供应商的任何逻辑模块。
public final class QueueHeader { private String que_name; // name of this queue private WaitLists waitlist; // pointer to wait lists private int nbr_waitlists; // number of wait lists private int nbr_wl_entries; // number of entries in a waitlist private int wait_time; // time to wait when no work private int nbr_threads; // total threads for this queue private QueueDetail[] details; // detail array of thread info // public application logic class for use by the Queue Thread public DemoCall to_call; // class to call |
应用程序线程类。
应用程序线程类包含线程逻辑。每个线程区域(清单 6 中的 QueueDetail
类)包含指向实际线程的指针。当一个 RMI 连接线程唤醒应用程序线程时,应用程序线程从队列的等待列表中取得请求。然后应用程序线程调用应用程序处理类的方法来执行工作并将从那个方法返回的对象传回给 RMI。
public final class QueueDetail { private int status; // status of this entry private String tname; // name of this thread private int totl_proc; // total requests processed private int totl_new; // total times instantiated private FrameWorkBase fwb; // base storage private QueueHeader AH; // Queue this belongs to private QueThread thread; // queue thread |
应用程序处理类。
一个单独的应用程序处理类包含了应用程序逻辑。我们前面所谈到的应用程序线程类调用关于应用程序处理类的方法。对于本示例,我们使用 DemoCall
接口(在清单 7 中)。实现该接口的任何类都是可以接受的。
public interface DemoCall { public Object doWork(Object input, FrameWorkInterface fwi) throws java.lang.Throwable; |
看一下该应用程序的 doWork()
方法的两个参数,我们可以知道:
FrameWorkParm
实例的引用,以 Object
类型传递(参见 清单 3 )。 FrameWorkImpl
)的引用,以 FrameWorkInterface
类型传递。第二个引用是这样的,应用程序可以称服务器 作为一个客户机。 这是 递归, 是编程中最有用的技术之一,而且有时也是最难实现的之一。 该框架后的基本原则是 公共内存 的概念。对于任何两个彼此通话的线程,必须使用它们之间是公共的内存。没有一个线程拥有该内存;该内存是可被所有的线程是可更新和可见的。
RMI 服务器是持久的。这意味着服务器所创建并且还没释放(活动引用)任何对象会为服务器的生命期内保留。当服务器启动时,它获得一个公共内存类( 清单 8 中的 FrameWorkBase
类 )的新的实例,并且分配一个带有该引用的私有的、静态的域。RMI 实现类( 清单 2 中的 FrameWorkImpl
类)也获得对公共内存类的引用。通过这种方式,所有的运行在服务器的线程 ― RMI连接线程和应用程序线程 ― 都可以访问该公共的内存。
图 2 是公共内存和使用它的线程的图示。
对,使用 Java 内存和垃圾回收是有点难理解。我们马上将做演示。然而,在我们开始前,我们需要讨论一下线程是如何彼此发现的。
RMI 连接是如何找到应用程序队列并唤醒一个应用程序线程的呢?应用程序线程又是如何唤醒 RMI 连接线程的呢?答案在于环境的结构和指针的使用。
锚
公共内存的基础是 FrameWorkBase
类(参见清单 8)。该类包含对其它类的 静态 引用。对于此示例来说,该域是公共的。(这仅是建立公共内存的一种方式。)
public final class FrameWorkBase { // Function Array public static FuncHeader func_tbl = null; // Remote Object myself (for recursive calls) public static FrameWorkInterface fwi = null; |
RMI 服务器启动(在 清单 4 中是 FrameWorkServer
类)实例化公共内存(类 FrameWorkBase
)的基础,并分配一个类域给 FrameWorkBase
对象的引用。因为引用是活动的,而且服务器是持久的,所以 JVM 不垃圾收集对象。考虑将启动类字段当作锚点。
因为启动类将对 FrameWorkBase
的引用传递给实现类的构造函数(在 清单 2 中是 FrameWorkImpl
类),而且实现类将 FrameWorkBase
引用保存在它的实例域,所有的 RMI 连接线程都可以访问 FrameWorkBase
类。
链
既然我们已经为公共内存建立了一个锚,那么我们还需要将其他类链到锚上。用这种方式,任何 RMI 连接线程(在 清单 2 中是与实现类关联的线程)都可以找到应用程序线程。
一个 RMI 连接线程使用它的实例域引用来定位 FrameWorkBase
类(参见 清单 8 )。然后该 RMI 连接使用 FrameWorkBase
实例域引用, func_tbl
,来访问函数数组(清单 9 中的 FuncHeader
类)。
public final class FuncHeader { private int nbr_func; // number of functions private FuncDetail[] details; // detail entries |
FuncHeader
details 数组(相继的被检查)包含一个元素代表每个框架所支持的函数(清单 10 中的 FuncDetail
类)。
public final class FuncDetail { private String name; // Function name private long used; // times used private QueueHeader qtbl; // queue |
每个 FuncDetail
包含:
QueueHeader
类) 每个队列(参见 清单 5 )包含:
通过搜索可用线程的详细信息数组,RMI 连接线程可以找到请求的可用的线程区域( 清单 6 中的 QueueDetail
类)。
每个线程区域( 清单 6 中的 QueueDetail
类)包含指向物理线程的指针。RMI 连接线程使用物理应用程序线程的引用来调用一个同步方法,该方法发出一个对象通知来唤醒应用程序线程。
查找应用程序线程:一个普通的示例
我们已经看到了很多的类和很多的指针 ― 下面还有很多内容。这里用中文做一番描述。
当 client 调用一个远程方法时,RMI 连接线程(将对 FrameWorkBase
类的引用作为实例域)(参见 清单 2 ):
FrameWorkBase
类 FrameWorkBase
实例域引用来定位函数数组 notify()
应用程序线程并等待它的完成 注意: 最后一步使用 notify()
。这并不是搞错了。大多数关于线程的书籍都会建议您使用 notifyAll()
。 notify()
和 notifyAll()
方法都是 Object
类的方法。问题是:对于一个特定的 Object
,有多少线程呢?对于 Queue 情况,每个实例仅有一个线程:
QueueThread qt1 = new QueueThread(); QueueThread qt2 = new QueueThread();
Notify()
唤醒一个任意的线程。但 qt1
和 qt2
域是引用。因为每个 QueueThread
对象仅有一个线程,所以 notify()
起作用。它还会唤醒一个任意的线程,但它仅有一个可供选择。
图 3 说明了顺着该链来查找应用程序线程的过程。
查找 RMI 连接线程
当应用程序线程处理完了时,应用程序需要查找正调用的 RMI 连接线程以将其唤醒。因为没有返回链,应用程序线程是如何知道谁调用它的呢?它不知道。这就是为什么应用程序线程必须要使用 notifyAll()
以及为什么在 RMI 连接线程要做些额外的工作了。
RMI 连接线程必须向应用程序线程传递些东西以唯一的标识自己。RMI 连接线程所创建和通过引用传递给另一个线程的任何对象对于其它线程来说都是可用的。在一个同步事件(记得与本文有关的 How threads affect common memory )后,然后每个线程都可以访问到对象的当前值了。对于本示例,框架使用了一个整数数组(参见清单 11)。
在 RMI 连接线程为请求找到适当的队列后,它把一个 增强的请求 放进队列的等待列表。这是来自参数 FrameWorkParm
(参见 清单 3 )和清单 11 中的几个其它的域的客户机的对象:
// requestor obj (monitor) to cancel wait Object requestor = this; // integer array created by RMI-Connection thread pnp = new int[1]; pnp[0] = 0; // passed object from the Client Object input_from_client = client_data; // returned object from the application thread Object back; |
requestor
:为使用对象等待和对象通知方法,RMI 连接线程和应用程序线程都必须可以访问实现类 Object
。 pnp
:Java 语言通过引用来传递数组。RMI 连接线程将第一个整数设置为 0。当应用程序完成了任务后,它将第一个整数设置为 1。 input_from_client
:这是在参数(参见 清单 3 )中传递给 RMI 服务器的客户机对象。 back
:由应用程序处理类返回的可选对象。 当客户机调用一个远程方法时,RMI 连接线程:
pnp[0] = 1
)以及 notifyAll()
方法的一个引用(requestor) // Wait for the request to complete or timeout // get the monitor for this RMI object synchronized (this) { // until work finished while (pnp[0] == 0) { // wait for a post or timeout try { // max wait time is the time passed wait(time_wait); } catch (InterruptedException e) {} // When not posted if (pnp[0] == 0) { // current time time_now = System.currentTimeMillis(); // decrement wait time time_wait -= (time_now - start_time); // When no more seconds remain if (time_wait < 1) { // get out of the loop, timed out break; } else { // new start time start_time = time_now; } } } } |
注意: while
循环(在 // until work finished
处)在现在所处的位置是因为当任何应用程序线程发出 notifyAll()
时,Java 语言唤醒 所有的 RMI 连接线程。
另一方面,当处理完成时,应用程序线程做以下的事情(参见清单 13):
notifyAll()
) // get lock on RMI obj monitor synchronized (requestor) { // the object from the application processing back = data_object; // set posted pnp[0] = 1; // wake up all RMI-Connection threads requestor.notifyAll(); } |
自主请求的事件流程和计时请求的事件流程是一样的,除了自主请求没有要求 RMI 连接线程等待完成外。在唤醒了应用程序线程后,RMI 连接线程返回给客户机一个“它已经被调度”的消息。自主请求作为结果看起来也许很简单,但是存在一个隐藏的困难。
来自应用程序处理类( 清单 7 )的 doWork()
方法的返回数据会发生些什么呢?它的返回数据到哪里去了并不该作为应用程序关注的事。开发者必须能够为计时的或是自主的请求使用相同的应用程序逻辑。所以,我们需要一个自主请求的代理。
代理 仅是另一个逻辑进程。换句话说,它是个有关联线程的队列。普通队列和代理队列的唯一不同在于 RMI 连接线程访问普通队列,而应用程序线程 可选的 访问代理队列。
应用程序处理类( doWork()
方法)可以返回一个对象到应用程序线程。对于自主请求来说,当想要时,应用程序线程可以通过创建一个新的增强的请求(用刚返回的对象)、将增强的请求放进代理的等待列表并唤醒一个代理线程来激活一个代理程序逻辑进程。这与 RMI 连接线程在根本上是相同的。
代理逻辑进程异步的完成,并且没有任何返回数据。代理队列应用程序处理类是放向后调用、向前调用或处理请求完成的任何其它的必要逻辑的地方。
任何异步进程的一个额外的关键要求是监控逻辑进程的方式。因为同步请求可能会超时,所以需要有一个释放内存的过程。自主请求执行无须客户机等待它们的完成;当自主请求不能完成时 ― 换句话说,当它们停止时 ― 很难检测出该停止。
监控环境的一种方式是使用一个定期扫描环境的 daemon 线程。把一个线程称为 daemon 仅仅是意味着它不是特定的应用程序或 RMI 连接的一部分。当监控线程发现问题时,它可以改正此问题,记录该问题的详细信息、发送一个消息给中间件队列或内部的通知另一个远程对象。该行为取决于应用程序,所以超出了本文的范围。
您需要一个彻底的方式来关闭服务器而不要管正在处理的请求。因为RMI 运行时包含从不结束的线程,所以结束 RMI 服务器的唯一的方式是计划性的使用一个系统退出方法或是使用一个操作系统清除。后者是最粗野的,而且要求手工的干预。
关闭 RMI 服务器的得体的方式是用一个关闭方法。但是,如果关闭方法仅结束 Java Virtual Machine,那么该方法的返回消息从不会返回客户机。更好的方式是启动一个关闭线程。该线程休眠大约两秒的时间来给线程的返回消息一个清除虚拟机的机 会,然后发出 System.exit(0)
。
也许冗长而难读吧。现在,在设法消化所有的这些信息之前,我们需要提出每个项目的三个重要的问题:
为回答第一个问题,框架为我们提供了能力:
框架将 RMI 线程环境从应用程序线程环境分离。另外,它还把应用程序线程从实际的应用程序逻辑分离。这是在面向对象设计中的非常重要的 更高级的抽象 。
它是否完成了它的承诺呢?正如您将看到的在与本文一起的 zip 文件(从 参考资料 部分下载)中所包含的代码,框架表现的极好。
第三个问题的答案,“哦,这很好,但……”也许投入所能得到的回报并不值得付出努力。结构的、关键的部分是错误恢复。有时非常规代码远比标准代码更重要。框架所需的是存在的更充分的理由。
如果我们能扩展这个简单的框架的话,由什么来支持每个请求的多队列呢?当一个客户机请求对资源的多访问时,如果我们能分割该请求,并把每个组件放进它自己的队列,那么我们就可以并行处理请求了。现在,这个可能性是无尽的。这是 请求代理(request brokering) ,它是下一节的主题。
一旦我们建立了基本的队列环境,马上就很明显,一些请求确实是包含了多个行为或组件。例如,完成请求也许会要求访问两个不同的数据库。我们可以以线性方式来访问每个数据库,但是对第二个的访问要等到第一个完成后才行。
处理多行为请求的更好些的方式是把请求分割成它的组件部件并把每个组件放入一个单独的队列。这样就可以并行处理了。这比线性编程要难,但是得到的益处远比预先的额外工作要重要。
我们需要什么来支持请求代理呢?我们要理解客户机请求不再是仅被单一的逻辑进程所关注的。所以,我们必须把客户机请求放进一个通用的区域,从而任何数量的逻辑进程都可以访问它。由于我们已经有了一个公共内存环境,所以我们现在必须将它增强。
这是个简单的部分。困难的工作一直是建立结构中第一个基础的块。在接下来的几节中,我们通过以下几种方式来增强单组件框架:
然后我们用新的框架来大致处理一个简单的请求。
我们需要一个公共的地方来放置来自客户机的同步请求和异步请求的详细信息。该公共信息包括:
FrameWorkParm
类的 client_data
对象) 所需的全部就是一个简单的对象数组。本例中的对象是 SyncDetail
类 AsyncDetail
类(参见清单 14)。
我们还需要把这两个类添加到公共内存的基础 FrameWorkBase
类。
public final class SyncDetail { private Object[] output; // output data array private Object input; // client input data, if any private int status; // 0=avail 1=busy private int nbr_que; // total queue's in function private int nbr_remaining; // remaining to be processed private int wait_time; // max wait time in seconds private Object requestor; // requestor obj to cancel wait private int[] pnp; // 0 not posted, 1 is posted public final class AsyncDetail { private Object input; // client input private QueueHeader out_agent; // agent queue, if any private int nbr_que; // nbr of queues in function private int nbr_remaining; // remaining unprocessed private int status; // 0 = available, 1 = busy private Object[] output; // output array |
这些对象所驻留的数组是个链表。该框架中的所有的动态数组都是链表。对链表条目的访问是直接通过下标。当一个线程将一个对象放进任何链 表时,该线程必须传给另一个对象的全部东西就是原整数(下标)。另外,在大量的使用中,扩展一个链表是很简单的 ― 在原来的链表上再链上一个新的链表。
为异步请求保存信息的另一个公共的地方是那些已停止的请求的动态数组。当同步请求花费了比用户可以等待时间的更长时,请求端的连接就终 止了。当一个异步请求所花费的时间多于允许的,处理可能就不能完成、请求可能就停止了。为了从停止状态恢复,就必须有一个地方来放置信息。这个地方就是 StallDetail
类(参见清单 15)。
我们还需要把这个类加到公共内存的基础,即 FrameWorkBase
类。
public final class StallDetail { private long entered; // time entered private int gen_name; // AsyncDetail subscript private int status; // 0 = available, 1 = busy private int failed_reason; // why it is here |
我们讨论了为多组件请求结构化环境的问题,但是这还不能作为 RMI 服务器如何知道什么组件是请求的部分的证据。
组件结构是开发者从开始就知道的信息。在基本的框架中,每个函数(逻辑进程的名称)都有单一的队列。在请求代理框架,每个函数有一个队列列表。与每个 函数 相联的 队列 的列表是组件结构。这是被改变了的 FuncDetail
类(参见清单 16)。当您编码自己的系统时,而不是使用演示系统,您将根据需要为每个函数建立一个结构。
public final class FuncDetail { private String name; // Function name private long used; // times used private QueueHeader agent; // optional agent queue private int nbr_que; // number of queues in this entry private QueueHeader[] qtbl; // array of queues |
从一个简单的公共内存环境开始,到现在我们已经通过增加几个类和修改其它的类(包括增加新的类到 FrameWorkBase
类)增强了该环境来支持请求代理。图 4 是支持该框架的最终公共内存结构。
对于任何的请求,RMI 连接线程:
client_data
的引用(参见 清单 3 )来创建一个 AsyncDetail
对象或是一个 SyncDetail
对象 AsyncDetail
或 SyncDetail
对象的整数下标根据优先级放进函数的每个队列的列表中 对于同步请求,RMI 连接线程等待直到所有的队列完成了处理。然后该框架将来自所有逻辑进程的对象连成一个单一的对象数组并把对象数组返回客户机。
对于自主请求,RMI 连接线程返回客户机一个“已经被调度”的消息。处理是异步发生的。当最后一个队列的应用程序完成了处理,应用程序线程将来自所有的逻辑进程的返回对象 选择的 连成一个单一的对象数组,并激活一个新的逻辑进程,即代理。代理应用程序可以检查那些返回对象并为支持请求而采取进一步的行动。例如,如果所有的逻辑进程都正常完成了,那么它将发出一个提交;否则,它将发出一个回滚。
我们已经讨论了很多关于线程和队列的东西了,也许还有点迷惑。没有什么能够象观看异步进程管理器运行更能减轻迷惑的了。现在到了把所有的东西放在一起演示的时候了。如果您还没有 下载 zip 文件 ,那么请现在就下载。
该演示要求至少是 Java 平台 1.1 版本。将文件解压缩到一个目录。结构如下:
Doc.html
,该文件为所有的类和运行时过程提供了文档 policy.all
文件 FrameWorkServer |
跟随启动一个单一访问的客户机(DemoClient_3,其函数是 F3)的指导,此客户机由三个队列组成(是命名为"第一次"的节)。
这是当你启动客户机时所发生的。客户机以 FrameWorkParm 对象作为参数调用远程对象 FrameWorkServer 的 syncRequest() 方法。 syncRequest()
:
SyncDetail
对象,并把它的下标符号放进队列 Q1、Q2 和 Q3 的等待列表中。 注意: 在标有 * 的步骤中,如果已经有了活跃的线程,那么 syncRequest()
所有做的就仅是 notify()
它。
当 syncRequest()
在等待时, 每个 应用程序线程:
SyncDetail
对象 doWork()
方法来执行队列的逻辑 SyncDetail
类中 wait()
直到下一个 notify()
。 当许多客户机同时的命中服务器时,令人兴奋的事情出现了!另外,没有可视化的工具的话,您就没有办法知道正在发生些什么。在本包中,有两个类正是提供了这样的一个工具。(即,命名为“装入”的指导部分。)
跟随指导来运行该可视化工具, FrameWorkThreads
类。
跟随指导来运行多客户机线程, DemoClientMultiBegin
类,来跟随系统加载。
在您处理好了服务器后,您可以用一个客户机请求, DemoClient_Shutdown
,得体的关闭它。
在本篇简洁的文章中,我们只能研究一下一个异步进程管理器的骨架。我们用 框架(framework) 这个词是因为这个词描述了一个骨架支持结构。一些可以补充该支持的元素有:
本文讲了很多的内容。没有人宣称建立一个后端框架是简单的。记住,Java 设计师在建立 EJB 和 GUI 容器上投入了非常多的努力。现在我们有什么呢?
我们将 RMI 逻辑从应用程序逻辑分离出来。通过这样做,我们开辟了一个应用程序队列和线程(并不局限于 RMI)的世界。这个世界使我们能:
然后我们把单处理环境增强为可以并行处理的请求代理。我们丰富了公共内存环境,从而:
从这点,RMI 服务器应用程序容器不再是空的了。