Huihoo.org - Open Enterprise Foundation

 Last Modified: 2003.05.09

使用Real-Time Java编写Real-Time程序


(by huihoo.org Cocia Lin)

概述

Real-Time Specification for Java缩写就是RTSJ。
RTSJ是Java的适应实时计算要求而开发。关于对实时系统的介绍和特性说明,请参见其他文章,这里重点是Java针对实时系统开发所做的改进做详细的介绍。
RTSJ在6各方面对Java做了增强:
1.增加实时线程。实时线程提供了比普通线程更完善和细致的控制属性和操作内容,例如更大的优先级范围,控制内存分配等等;
2.增加了新的工具类和编程方式,比如内存工具类。利用这些工具类,可以完成不需要垃圾回收的Java程序;
3.增加了异步事件处理器类和相应的机制。把异步事件和jvm外界发生的事件直接关联起来;
4.增加了异步传输的控制机制,允许一个线程更改另一个线程中的控制流。也就是说,可以中断或者继续另一个线程的运行;
5.增加一种机制,能够控制对象分配到内存的位置;
6.增加一种机制,能够访问特定地址的内存。

RTSJ对传统java的增强的过程中本着一个原则,就是不改变原有的java的任何语法。这样做的目的就是保持原有java的连续。也就是说,在普通jvm上能够运行的程序,理论上,可以完全相同的在rt jvm上运行;反过来则不行。之所以强调是在理论上情形,原因是现有的rt jvm还没有实现完全的java类库,比如awt,swing,text等。而一些常用的lang,util,io等都有实现。这也是现有的rt jvm选择实现的优先级别。
对于rtsj的核心和基础,我认为有以下的几点:
1.领域内存 Scope Memory
2.实时线程 Realtime Thread
3.高精度时间 High Resolution Time

这当然不是说,其他的不重要。其他的rtsj能力就像awt对你的重要程度一样,如果你在作客户界面程序,那awt对你就很重要;如果你在做servlet,jsp等形式的j2ee程序,awt可能对你完全没有意义。所以这里只介绍核心和基础的几点内容。

领域内存 Scope Memory

Rtsj增加领域内存根本目的就是解决jvm的垃圾收集问题。我们知道,jvm的垃圾收集机制具有不确定性。例如,我们不知道jvm什么时候将要进行垃圾收集,收集操作需要多长时间,收集线程本身占用多少内存,多少cpu时间,是否有其他的i/o资源占用等等。而实时应用程序的一个很重要的指标就是,能够预计一个操作的准确时间,占用的资源。rtsj要能够在实时应用中得到应用,首先就要解决垃圾收集这个头疼的问题。
Rtsj并没有放弃垃圾收集机制,如果真的这样做,也是不可接受的。Java能够存在和发展到现在的这个地步,头号功臣可能就是这种机制。它让开发者从细节内存管理的泥潭中解放出来。如果去掉这种机制,rtsj和c++又有什么区别?rtsj的做法是,让有实时要求的代码使用领域内存机制,避开垃圾收集,而没有此项要求的代码,和标准的java一样书写,享受垃圾收集给你带来的快乐。对我来说,可以自由的用碗来吃饭,而不用自己来刷碗,应该不算一件坏事。
Rtsj的这种做法,是基于这样的两条结论:不产生垃圾的代码不会导致请求式的垃圾收集;不引用堆中对象的代码可以抢占垃圾收集器的线程。
领域内存的机制,其实就是内存管理的机制,开发者自己管理内存的机制。对照上段的文字,你可能有了疑问:需要我自己管理内存,这又和我使用c++有什么区别?这个问题的答案,不同的人理解不同,最终结论就会有所不同。我的理解是,rtsj提供了比c++简单的和高级别的内存管理方式,是开发者学习几个工具类和一种编程风格,就能实现与c++等相近的效率和功能。你的理解是什么呢?这和Java公认的比c++慢很多倍,但却被人们普遍接受的情况,有着异曲同工之处。
读者脸色有变,现在进入正题。
为了实现内存管理,rtsj提供了一组内存管理类,和一种编程方法,或者说的编程规范。


图 1 内存管理类


ImmortalMemory的中文名字是不朽内存,他是静态的,或者说是与jvm共存亡的。只要jvm在运行,不朽内存就存在,并且可用。他和static静态的对象行为一样。存放在不朽内存中的对象不会被垃圾收集。但是要当心,不朽内存不会被回收,所以在其中放入太多的东西,会导致系统内存耗尽。所以,在有其他选择的情况下,不要使用不朽内存。
HeapMemory中文名字是堆内存。他和我们平时在java程序中使用new 这个关键字生成对象的行为是一样的。其中的对象数据在无用的时候会被垃圾回收。
ScopeMemory就是领域内存。他有各种不同的实现。这里只拿出两个典型的,并且已经被现有的几种rtjvm支持的领域内存实现:LTMemory和VTMemory。这两者的区别,是在他们分配存储空间的时间要求上,LTMemory随着分配空间的增加,分配的时间以固定的线性增长。在rtsj规范中,没有说明VTMemory相对LTMemory的任何优点,但规范对LTMemory的分配时间有严格要求,而对VTMemory没有。所以可以简单的这样理解:LTMemory分配时间很短,但内存使用效率可能不高;而VTMemory分配时间可能长一点,但内存使用效率可能会高。个人认为,现阶段不用考虑他们的具体区别,全部使用LTMemory也不会有任何问题,我以我的房东的房子做保证。
下面提供一段代码,示范一下,使用领域内存的情景。
import javax.realtime.*;
public class Foo {
class Bar implements Runnable{
// run()在运行sm.enter(this)时被调用
public void run(){
//newObject 被分配在LTMemory sm中
Object newObject = new Object();
}//方法退出后,newObject对象的空间立即释放
}
void createSomeObjects() {
//分配1024字节空间
ScopeMemory sm = new LTMemory(1024, 1024);
// 1.直接分配
Bar b = (Bar)sm.newInstance(Bar.class);
// 2.进入领域
sm.enter(b);
}//在这里,垃圾收集器准备回收sm
//
//这样的情形下,sm.enter()方法中分配的对象,
//在运行期间即使引用悬空,也不会被垃圾收集,
//而在此方法退出后,其中所有的分配空间被立即释放,
//也不通过垃圾收集机制。

Public static void main(String[] args){
  :Foo foo = new Foo();
  Foo. createSomeObjects();
}
}
这个例子比较简陋,但他是完整的,并且说明了rtsj编写程序的几个两个基本要求:内存管理类的使用和程序编写风格要求。内存管理类在前面的例子程序的注释中有所介绍。而所谓程序编写风格,其实就是sm.enter(b)的使用的方式,包括Bar需要实现run方法。这样的风格称做closure,中文为闭包。闭包的意思是,一个程序段相对的独立和封闭,这样的程序段可以很容易的控制内存这样的资源的分配控制。比如,我在进入这个程序段时准备好内存空间,例子中的分配空间是1024字节,在退出这个程序段时将这些内存释放掉,因为在闭包的环境下,相当于告诉jvm,过了这段程序,这些内存空间中的对象对我已经没有用了。
我们来假想一下sm.enter(b)中的情形:
enter( b ){
prepareResource(b);
b.run();
releaseResource(b);
}

对于内存管理,浅尝则止,只说到概念。这些内容可能还不足以开始真正的工作,还有很多内存管理方面内容没有说明。更详尽的内容描述可能在其它的更深入的资料中找到。

实时线程 Realtime Thread

在介绍RealtimeThread之前,我们先来看一个HelloWorld.
Import javax.realtime.*;
Public class Hello{
Public static void main(String[] args){
RealtimeThread rt = new RealtimeThread(){
Public void run(){
System.out.println("Hello RealTime World!");
}
};
//准入控制,分析可行性。如果发现此线程某些资源不能得到,则退出。
//注意:分析可行性是一项可选功能,有的rtsj jvm可能没有实现,
//但是这个特性确实很吸引人。
if(!rt.getScheduler().isFeasible()){
System.out.println("rt thread is not feasible.\nsorry!");
System.exit(1);
}else{
rt.start();
}
//
try{
rt.join();
}catch(InterruptedException e){}
System.exit(0);
}
}

图 2 实时线程类


从上图中可以看出,RealtimeThread扩展了Thread,也就是说,他具有了普通Thread所有的特性和功能。
说到线程,就不得不说到线程的优先级了。普通java的线程有1-10,10个优先级,而rtsj的线程有32个优先级。而且只要是RealtimeThread的优先级,肯定比普通Thread优先级高。换句话说,RealtimeThread优先级肯定大于10。
RealtimeThread相对于Thread,增加了更多的细致的控制能力,这些从RealtimeThread的构造函数中,就能看得出来。
public RealtimeThread(SchedulingParameters scheduling,
ReleaseParameters release,
MemoryParameters memory, MemoryArea area,
ProcessingGroupParameters group,
java.lang.Runnable logic)
1.SchedulingParameters 调度参数:此参数中可能包含优先级信息,此线程可能就按照这个优先级运行;
2.ReleaseParameters 释放参数:释放参数在线程使用waitForNextPeriod方法和要求使用准入控制的时候起作用;
3.MemoryParameters 内存参数:此参数可以约束线程对领域内存的使用。
4.MemoryArea 内存区域:可选择的有,堆,不朽内存,领域内存;
5.ProcessingGroupParameters 处理组:用来控制非周期性的活动;
6.Runnable 逻辑:也就是Runnable对象。

这六个参数这样介绍,真是糊里糊涂。现在用一个例子,看看能不能帮助你。

//Note:此代码引自<实时Java平台编程>,page 117
import javax.realtime.*;
public class FullConstr{
public static void main(String[] args){
//1.将优先级信息封装在此参数中
SchedulingParameters scheduling =
new PriorityParameters( PriorityScheduler.MIN_PRIORITY + 20 );
//在线程执行完一个周期之后,也就是waitForNextPeriod时
//会使用此参数中的信息。这里全部用null构造,就是不起作用
ReleaseParameters release =
new AperiodicParameters( null,null,null,null );
//指定内存使用的类型
MemoryParameters memory =
new MemoryParameters( MemoryParameters.NO_MAX,//Heap
0);//Immortal memory
//内存区域。这里使用了堆内存,和我们使用new没有区别
MemoryArea area = HeapMemory.instance();
//这里不讨论此参数。虽然不能靠这个参数帮助你,
//但我保证,特不会妨碍你。
ProcessingGroupParameters group = null;
//这个参数熟悉吧,就是我们要运行的代码。
//这里使用MyThread,假设已经准备好了要运行的代码。
Runnable logic = new MyThread();
RealtimeThread rt = new RealtimeThread(scheduling,
release,
memory,
area,
group,
logic);
rt.start();
try{
//等待此线程,直到结束。
rt.join();
}catch(Exception e){};
}
}

实时线程中,还有一个重要的问题是,资源的抢占和优先级的关系。现在的rt jvm实现中,实现的都是优先级继承机制。Rtsj推荐的另一种抢占机制是优先级限高机制。这里不作详细介绍。这些内容会在本文的后续专题文章中出现。
在rtsj中,很多问题的解决方法都是衍生于实时线程的,例如事件触发机制。这里所说的事件,扩展了java awt & swing中的事件思想,rtsj把jvm外部的事件也映射到jvm中,例如i/o的网络操作。为了实现高效的事件操作,体现实时特性,就需要借助于实时线程,线程池这样的技术。异步事件,异步传输的控制机制就是结合实时线程,加上各种资源的封装分配的结果。这样说来,实时线程是rtsj很基础的内容。
线程介绍了这么多,是不是觉得有点复杂?对,不但复杂,而且能完成更多的工作。如果你能够把这两段程序运行一下,并试着根据api修改一下,我认为,本文的介绍目的就达到了。
浅尝则止。

高精度时间 High Resolution Time

这里所说的高精度时间,是相对于普通java而言。普通java的时间精度是毫秒,而rtsj的高精度时间可表示的精度为纳秒。
高精度时间用一个64位表示的毫秒值和一个32位表示的纳秒值组成,时间长度就是:
10e64 (毫秒) + 10e32 * 1000 (纳秒) = 时间总和 (毫秒)
这个时间总和相当于大约2.92亿年。
这样,从时间精度上和时间表示长度上,都对以前的java时间做了很大的提升。
不过,现在的软件系统能够响应1个毫秒级也决非易事,更何况是纳秒了。这可能是rtsj规范的制定者对千年虫问题还是心有余悸的缘故吧。

图 3 高精度时间类

HighResolutionTime是这些类中的抽象基类,提供了最基本的时间方法的实现。
AbsoluteTime表示绝对时间,例如2003年5月8日23:06:10 .177 就是一个绝对的时间,如果有需要,这个时间可以精确到纳秒级。可能在这个时间需要引爆一个原子弹,也可能这个时间我正写的头昏脑胀需要大喊一声,不管怎样,这个时间可是纳秒不差。
RelativeTime是一个相对时间,一个时间段,一个期限,一个deadline time。这个功能类的强项就是时间的前推后算。对于实时程序而言,很多的操作需要设置超时时间。比如,一辆汽车正在驶向悬崖边缘,要求必须在15个纳秒内启动转向停车操作,如果在这个规定期限内此操作没有完成,那就让驾驶员跳车吧,否则人财两空。这个15纳秒就用RelativeTime来表示,也只有这个工具类可以表示这样精度的时间段。但在我看来,汽车比我贵重多了,所以,如果是我,我就把转向停车操作的时限调到2.92亿年,誓与汽车共存亡。
RationalTime表示的是,在一个时间段内,某个操作发生的频率。从表现来说,他的行为类似于Java的Timer类,不同的时,RationalTime表示的时间精度更高。
关于高精度时间,这里没有给出例子。原因是我觉得他们和普通Java中提供的工具类太想象了,不同的就是时间精度。而Real-Time本身强调的很多问题就是时间的问题,所以在这里,才把时间部分的工具类单独拿出来作介绍。

告一段落

本人能力有限,如果你觉得没有把这些东西说明白,也可以理解。不过这些东西也是不太容易理解的。光说不练肯定不行。所以现在你还是没有搞清楚,也不用多浪费脑细胞。亲手试试,也许会有豁然开朗的感觉。
在前面也说过,本文重在概念性方面。具体每一个技术细节问题,后续会有相应的专题文章介绍。你也可以参考相关的书籍和资料。

参考


Huihoo Power!

企业计算研究中心
http://www.huihoo.com

开放企业基金会
http://www.huihoo.org

Real-Time Java Expert Group
http://www.rtj.org

Reference Implementation,RTSJ JVM
http://www.timesys.com

<<实时Java平台编程>>
Peter C. Dibble
腾启明 等译
机械工业出版社

Efficient MemoryReference Checks for Realtime Java
Angelo Corsaro and Ron K. Cytron
Department of Computer Science and Engineering
Washington University
St. Louis, MO, 63130, USA
{corsaro, cytron}@cse.wustl.edu

Doing Real-Time With Java
Kelvin Nilsen, Chair of the Real-Time Java Working Group