实时 Java,第 6 部分: 简化实时 Java 开发

引入 Lifecycle Memory Managed Periodic Worker Threads 模式

由于实时 Java™ 虚拟机支持作用域内存,因此为作用域内存使用定义通用模式可以提高开发人员的生产力。这些模式通过降低作用域核心功能的复杂性,减少了了解和直接使用作用域的需求。本文是 实时 Java 系列文章 的第 6 篇(也是最后一篇),文中将引入 Lifecycle Memory Managed Periodic Worker Threads 模式作为简化实时 Java 开发的模型。通过一个示例实现和简单的示例应用程序论证了该模式的可行性。

Michael Dawson, 咨询软件开发人员, IBM Ottawa Lab

Michael DawsonMichael Dawson 在 1989 年毕业于 Waterloo 大学,获得计算机工程学士学位,在 1991 年从 Queens 大学获得了电子工程的硕士学位,专长于密码技术。他曾从事过安全顾问工作并开发了 EDI 安全产品。随后,他领导开发了一个启动工程,该工程用于跨多个平台交付安全产品。从此成为团队领导者,该团队从事电子商务应用程序的开发并将其交付为各种服务,包括 EDI 通信服务,信用卡流程、在线拍卖以及电子发票等。涉及到的技术包括 C/C++、Java、J2EE 平台以及跨越多个操作系统的组件。在 2006 年,Michael 加入了 IBM 并从事 J9 JVM 和 WebSphere Real Time 的开发。



2007 年 8 月 13 日

Java 实时规范(RTSJ)提供了一些扩展以简化使用 Java 语言开发实时(RT)应用程序。用于应用程序管理堆外内存区域和避免垃圾收集(GC)导致的延迟的机制就是一个此类的扩展。这些内存区域称为作用域 内存和不朽 内存。为确保堆和不朽内存区域的完整性,RTSJ 定义了使用作用域内存的规则。这些规则控制了作用域之间的许可关系,以及作用域中的对象对其他内存区域中的对象的引用。这些规则使作用域能够在广泛的设计模式和应用程序中使用,但是这种灵活性的代价是增加了复杂性。结果,一些开发人员发现使用作用域比较困难而且易于出错。本文引入的这种技术可以让您简化作用域内存的使用。

Lifecycle Memory Managed Periodic Worker Threads 模式

RT 系统中的一个常见需求是,一组线程需要周期性地执行并且相互协作以完成某些任务。RTSJ 包含了专门针对周期性执行线程的机制。Lifecycle Memory Managed Periodic Worker Threads (LMMPWT) 模式的目标是提供一种简化的内存管理模型,处理很多周期性线程不能被 GC 中断的用例。

LMMPWT 模式要求

要适用于实际应用程序,LMMPWT 模式必须至少满足以下要求:

  • 提供一个或多个协作完成一项任务的线程。
  • 确保执行不被 GC 中断。
  • 提供协作线程的通信机制。
  • 提供对象的生命周期管理。

内存管理模型

LMMPWT 模式将可用的对象生存期约束为对周期性线程最有用的时间,同时最小化对象的生存期和所需的总内存。受支持的对象生存期有:

  • Retain Forever:具有此生存期的对象不进行垃圾收集并且所有线程都可以访问该对象。
  • Retain Thread Group:只要协作线程组中的任意一个线程没有终止,就可以使用具有此生存期的对象。这些对象只供线程组中的线程访问。
  • Retain Thread:只有在运行某个特定线程时才可以使用具有此生存期的对象,而且此类对象只供该特定线程访问。
  • Retain Iteration:只有在线程执行其任务的某个特定迭代时才可以使用具有此生存期的对象,该线程只能在创建此类对象的迭代的执行期间访问这些对象。

对于一组典型的、通过协作完成一组任务的周期性线程而言,这个内存管理模型很适用。每个线程的逻辑遵循执行 > 等待下一周期 > 执行 > 等待下一周期…… 的模式。在每个执行(或者,在此将其称为迭代)期间,线程可以分配很多对象。有些对象只在迭代期间需要,有些在后续迭代中需要,有些可能要求和协作线程组中的其他线程共享,而有些则可能需要和组外的线程共享。该模型通过允许开发人员创建具有适当生存期的对象来满足这些需求。

此模型简化了应用程序设计。您不用考虑对象需要使用何种作用域,只是决定对象的保留时间即可。这样虽然限制了内存管理的粒度,但是在多数情况下,简单性、可维护性和易于开发的优点超过了细粒度控制的优势。可以预见的是,应用程序所需的大多数对象都会被分配为 Retain Iteration 生存期,说明在每个迭代结束时都会释放这些对象。

要维护具有不同生存期的对象的完整性,在引用这些对象时必须遵守一条规则:一个对象可能不能引用另一个具有更短生存期(与自身比较)的对象。这与 RTSJ 中定义的作用域规则一致。并且可以通过底层的作用域实现进行实施。表 1 中给出了该规则的总结:

表 1. 引用具有不同生存期的对象的规则
包含引用的对象的生存期是否允许引用具有 Retain Forever 生存期的对象?是否允许引用具有 Retain Thread Group 生存期的对象?是否允许引用具有 Retain Thread 生存期的对象?是否允许引用具有 Retain Iteration 生存期的对象?
Retain Forever
Retain Thread Group

Retain Thread

Retain Iteration

任意内存管理模型都有一个关键元素,即控制收集对象和将内存返回给系统的时机。表 2 针对每个受支持的对象生存期对模型的这个方面进行了概述:

表 2. 每个对象生存期何时收集对象和返回内存
对象生存期何时收集对象和恢复内存

Retain Forever

从不。

Retain Thread Group

当线程组中的所有线程终止时,收集具有此生存期的所有对象,并将分配给该组的内存返回给系统。

Retain Thread

当线程终止时,收集具有此生存期的所有对象,并将分配给该线程的内存返回给系统。

Retain Iteration

当线程的当前迭代终止时,收集具有此生存期的所有对象。该内存区域留给线程的下一个迭代使用。当线程完成其最后一个迭代并终止时,将分配给该线程的内存返回给系统。

管理对象

必须给分配的对象设置生存期。为使针对平均每个对象所需执行的工作最小化,默认设置应使需要指定生存期的对象个数最少。Retain Iteration 似乎最适合设为默认设置,因为只有应用程序需要对象时才在迭代之外保留对象。使用 Retain Iteration 让您可以忽略应用程序及其调用的方法创建的临时对象。对于必须保留的对象,不管怎样都需要确认和了解这些对象的需求,因此指定其生存期只是稍微增加了一点必需的工作。

一般情况下,使用以下语句创建对象:

Object ref = new Object();

理想情况下,您应该可以使用同样的代码外加一些附加标记进行创建,如下所示:

/*[Lifetime=Retain Thread]*/
Object ref = new Object();

最佳的机制可能取决于开发环境,可以使用(比如)预处理器、注释或 helper 对象实现。模型并不取决于所选的机制;但是机制应使您能够指定生存期而不需要了解作用域,并且应该避免代码混乱从而避免影响可维护性。在本文的 使用作用域的实现 一节中我将讨论一种可能的方法。

您还需要能够存储对具有不同生存期的对象的引用。具体说来,您会希望在一个迭代中创建具有 Retain Thread 或 Retain Thread Group 生存期的对象,并在后续的迭代中访问这些对象。具有不同生存期的对象之间的引用规则可能会禁止线程对象自身的成员变量包含对这些对象的引用。模型需要包含一种机制,将根对象传递给用于存储和检索生存期为 Retain Thread Group 或 Retain Thread 的对象的每个迭代。


使用作用域的实现

您可以使用标准的 RTSJ 作用域和不朽内存区域实现前面提出的模型。使用作用域支持具有已定义生存期的对象,如表 3 所示:

表 3. 用于每个对象生存期的内存区域
对象生存期所用内存区域

Retain Forever

不朽内存。

Retain Thread Group

启动一组线程时分配的顶级作用域。

Retain Thread

启动线程时分配给线程的作用域,保留在线程作用域堆栈中,直至线程终止。

Retain Iteration

分配线程时分配给线程的作用域。每个迭代都需要进入和退出作用域,因此在迭代之间将清除作用域。

当线程执行迭代时,作用域堆栈应如图 1 所示:

图 1. 作用域堆栈
作用域堆栈图

示例实现

下面的示例 API 类演示了前面提出的模型使用这种基于作用域实现的用法:

  • PeriodicThread
  • PeriodicThreadGroup
  • ObjectRootsHelper
  • PeriodicThreadDescriptor
  • StartPeriodicThreads

PeriodicThread 是基类,从它派生出协作线程组中的 worker 线程。它在幕后为您管理作用域。它定义了以下常量,使用这些常量可以指定对象的生存期:

  • RETAIN_FOREVER
  • RETAIN_THREAD_GROUP
  • RETAIN_THREAD
  • RETAIN_ITERATION

此类的 run 方法是 final 类型,因此派生类不能使用它指定自己的逻辑。相反,您应该重写下面的方法使之包含线程的逻辑:

threadLogic(ObjectRootsHelper helper)

每次线程执行迭代逻辑时调用此方法。

传入 ObjectRootsHelper 的一个实例帮助线程管理生存期超出迭代的对象。因为线程自身可能在作用域外(本例中指 Thread Group 的作用域)进行分配,所以也许不能使用它存储对具有某些生存期的对象的引用。ObjectRootsHelper 提供了以下方法,使用这些方法可以存储和检索对这些对象的引用:

  • void add(int lifetime, String key,Object obj)
  • void addSynchronized(int lifetime,String key, Object obj)
  • Object get(int lifetime, String key);
  • Object getSynchronized(lifetime,String key);

ObjectRootsHelper 支持管理具有 RETAIN_THREAD_GROUPRETAIN_THREAD 生存期的对象。

因为要针对每个迭代调用 threadLogic 中的逻辑,所以线程需要一种机制以表示已完成最后一个迭代并且应该终止线程。提供的 setTerminate() 方法使线程能够告知框架何时终止线程。

PeriodicThread 还包含了以下方法:

public Object allocate(final Class classObject, 
                       int lifespan, 
                       final Object ... parms)
   throws IllegalArgumentException

allocate() 方法让您能够分配具有给定生存期的对象。例如,不需要参数时:

token = (WatchedThreadToken) allocate(WatchedThreadToken.class,
                                      PeriodicThread.RETAIN_THREAD_GROUP);

或需要参数时:

watchedThreads = (WatchedThreads) allocate(WatchedThreads.class,
                                           PeriodicThread.RETAIN_THREAD,
                                           new PrimInt(10),
                                           new PrimInt(3));

请注意原语的传入方式。当类的构造函数包含原始参数(longintfloatdoubleshort)时,可变长实参列表要求您传递 com.ibm.realtime.easyrtj.prmitive.PrimXXX 而不是原语本身。

如果线程创建的对象需要在内部执行分配,您必须将对线程本身的引用传递给对象以便在需要时能够调用 allocate() 方法。

PeriodicThread 提供了一个构造函数:

PeriodicThread(SchedulingParameters scheduling, 
               RelativeTime period,
               Long threadMemorySize,
               Long iterationMemorySize,
               PeriodicThreadGroup group)

此构造函数是公有的,因为 PeriodicThread 必须扩展子类。但是,您不需要直接调用构造函数。为您提供的 PeriodicThreadDescriptorStartPeriodicThreads helper 类使您不需要直接构建 PeriodicThreads

PeriodicThreadDescriptor 类描述了一个线程作为线程组的一部分启动。该线程提供了一个公有构造函数:

PeriodicThreadDescriptor(int priority, 
                         Class classObject, 
                         long threadMemorySize, 
                         long iterationMemorySize, 
                         long period)

此构造函数让您能够指定优先级、内存大小、线程周期和表示要运行的线程的类(扩展 PeriodicThread 的类)。

构建一组 PeriodicThreadDescriptor 并将它们传递给 StartPeriodicThreads 的静态 startThreads 方法,并且内存的大小由线程之间共享。startThreads 按描述符中的定义启动线程,以便共享指定大小的内存(即,共享生存期为 RETAIN_THREAD_GROUP 的对象)。

总之,使用示例类,您将:

  1. 创建一个或多个类作为 PeriodicThread 的子类,将所需的逻辑放入 threadLogic 方法。
  2. 为每个要运行的组线程创建 PeriodicThreadDescriptor
  3. 通过调用 startThreads,传入描述符数组以启动线程。

示例 watchdog 代码

典型的 RT 用例拥有协作执行一组任务的线程组,并使用 watchdog 确保线程的持续活动性并执行其任务。每个线程必须周期性地通知 watchdog 以表示线程仍然是活动的。如果在一个给定的时间间隔中拥有不活动的线程,则一个监控线程将发出警报。

示例 watchdog 代码(参见 下载)定义 WatchedPeriodicThread 作为 PeriodicThread 的子类。此类的 threadLogic 方法是 final 类型,而子类将逻辑置于 watchedLogic 方法中运行。传入 threadLogic 中的 ObjectRootsHelper 被传给 watchedLogic 方法。同样,子类必须实现 getThreadName 以返回线程关联的惟一名称。

WatchedPeriodicThreadthreadLogic 中添加代码以创建 WatchedThreadToken 对象,其生存期为 RETAIN_THREAD_GROUP。然后使用 ObjectRootsHelper 存储该对象(以线程的名称作为键),从而与其他线程共享这个对象。每次运行 threadLogic 时,它都会运行子类提供的 watchedLogic method,然后在它的 WatchedThreadToken 中调用 kick 以表示线程仍然是活动的。

WatchDogThread 分配了一个生存期为 RETAIN_THREADWatchedThreads 对象并使用该对象跟踪它所监视线程的活动情况。每次为 WatchDogThread 调用 threadLogic 时,它将验证监视的线程是否仍然是活动的,如果有线程已经终止就发出警报。(它只使用 printf,但是也可以轻松地发送一个(比如)SNMP trap。)

Thread1 是一个周期性线程(扩展 WatchedPeriodicThread),运行 10 个迭代后由于 “意外” 异常而终止。 Thread2 是一个简单的周期性线程(扩展 WatchedPeriodicThread),它会一直运行下去。

TestMain 类使用 StartPeriodicThreads.startThreads 启动 Thread1Thread2WatchDogThread。运行测试程序可以显示,WatchDogThread 开始监控 Thread1Thread2,然后在 Thread1 终止时发出警报。

此示例演示了模型的使用,其中的对象具有 RETAIN_THREAD_GROUPRETAIN_THREADRETAIN_INTERATION(默认)生存期。

下载 提供了一个 JAR 文件,其中包括了示例实现和 watchdog 应用程序的源程序和已编译的类。要运行示例,请通过 RT 虚拟机使用以下命令行:

java -cp LMMPWTSample.jar com.ibm.realtime.easyrtj.sample.TestMain

结束语

RT 垃圾收集器之类的替代方法可以减少使用作用域的要求,但是在某些应用程序中仍然可能需要使用作用域。本文引入了 LMMPWT 模式以简化作用域内存的使用。为阐明模式的可行性和有用性,概述了一个使用作用域的示例实现,并提供了一个示例应用程序,展示模型与具有各种生存期的对象结合使用的情况。


下载

描述名字大小
示例源代码j-rtj6.jar85KB

参考资料

学习

获得产品和技术

  • WebSphere Real Time:WebSphere Real Time 利用了标准的 Java 技术并且没有损失确定性,使应用程序依赖于精确的响应时间。
  • Real-time Java 技术:访问作者的 IBM alphaWorks® 研究站点,查找用于实时 Java 的先进技术。

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Java technology
ArticleID=247897
ArticleTitle=实时 Java,第 6 部分: 简化实时 Java 开发
publish-date=08132007