使用实时 Java 进行开发,第 1 部分: 探索实时 Java 的独特功能
2009-10-19 00:00:00 来源:WEB开发网实时 Java 是对 Java 语言的一组增强,为应用程序提供了一定程度的实时性能,这些实时性能是标准 Java 技术所不能提供的。传统的吞吐量性能通常是对可在固定时间量内完成的指令、任务或工作的总数的衡量。与传统的吞吐量性能不同,实时性能专注于应用程序(在不超出给定时间约束的情况下)响应外部刺激因素所需的时间。在硬 实时系统中,决不能超出这类约束;软 实时系统对违规具有更高的容忍度。实时性能要求应用程序本身控制处理器,以便它能够响应刺激因素,并且在响应刺激因素的同时,虚拟机内的竞争进程不会阻止应用程序代码的执行。实时 Java 在 Java 应用程序中交付了前所未有的响应能力。
实时 JVM 可利用实时操作系统(real-time operating system,RTOS)服务来提供硬实时功能,或者可以为具有比较软的实时约束的应用程序运行一个或多个传统操作系统。在使用实时 JVM 时,可以免费使用实时 Java 中使用的一些技术。但是为了探索实时 Java 中的一些特性,需要对应用程序进行一些更改。这些特性是本文介绍的重点。
必须约束的子进程
JVM 服务是一个执行工作的给定应用程序,这些工作仅能被该应用程序松散地控制。一些运行时子进程在 JVM 内部运行,包括:
垃圾收集:此任务用于收回应用程序不再使用的运行时内存块。垃圾收集可以使应用程序执行延迟一段时间。
类加载:此进程(之所以称为类加载,是因为 Java 应用程序是在类粒度级别加载的)涉及从文件系统或网络加载应用程序结构、指令和其他资源。在标准 Java 中,应用程序在第一次引用一个类时加载这个类(延迟 加载)。
即时(Just-in-time,JIT)动态编译:许多虚拟机在应用程序运行时通过动态编译将方法由 Java 字节码解释为本地机器指令。尽管这可以提高性能,但编译活动本身可能导致临时延迟,阻止应用程序代码运行。
调度:在标准 Java 中,应用程序只有极小的控制权限来调度自己的运行线程,以及调度与在同一操作系统上运行的其他应用程序相关的应用程序。
所有这些子进程都可能限制应用程序响应外部刺激因素的能力,因为它们可能延迟应用程序代码的执行。例如,可以调度一个指令序列来响应来自网络、雷达系统、键盘或任何其他设备的信号。实时应用程序具有一段很短的可接受时期,在此期间,允许不相关的进程(比如垃圾收集)延迟响应指令序列的执行。
实时 Java 提供了各种技术,旨在最小化底层子进程对应用程序的干扰。切换到实时 JVM 时可使用的 “免费” 技术包括:限制了收集操作的持续时间和干扰影响的专门垃圾收集,允许在启动时优化性能(而不是延迟优化)的专门的类加载,专门的锁定和同步,以及能够避免优先级反转的专门的优先线程调度。但是,可能需要对应用程序进行一些修改,要利用 Java 实时规范(Real-Time Specification for Java,RTSJ)引入的特性时更应如此。
RTSJ 提供了一个支持 JVM 中大量实时特性的 API。一些特性在规范实现中是强制性的,另一些是可选的。规范包括以下一般区域:
实时调度
高级内存管理
高精度计时器
异步事件处理
异步线程中断
Realtime 线程
RTSJ 定义了 javax.realtime.RealtimeThread — 标准 java.lang.Thread 类的一个子类。从本质上讲,RealtimeThread 支持规范中的一些高级特性。例如,实时线程受实时线程调度器控制。该调度器提供了一个独特的调度优先级范围,可以实现先入先出的实时调度策略(确保最高优先级的线程不会受到干扰),以及优先级继承(该算法可阻止较低优先级线程无限期地持有需要不受干扰地运行的较高优先级线程所需的锁,这种情形称为优先级反转)。
可以在代码中明确构造 RealtimeThread 的实例。但是也可以轻松更改应用程序来启用实时线程,从而避免繁重的开发工作和相关成本。这里给出了干扰最小且最透明地启用实时线程的各种方式的示例。这些技术使应用程序能够最轻松地利用实时线程,使应用程序能够保持与标准虚拟机兼容。
按优先级分配线程类型
清单 1 给出了一段代码,这段代码根据优先级值分配一个实时或常规线程。如果在实时虚拟机上运行,一些线程可能是实时线程。
清单 1. 根据优先级分配线程类型
import javax.realtime.PriorityScheduler;
import javax.realtime.RealtimeThread;
import javax.realtime.Scheduler;
public class ThreadLogic implements Runnable {
static void startThread(int priority) {
Thread thread = ThreadAssigner.assignThread(
priority, new ThreadLogic());
thread.start();
}
public void run() {
System.out.println("Running " + Thread.currentThread());
}
}
class ThreadAssigner {
static Thread assignThread(int priority, Runnable runnable) {
Thread thread = null;
if(priority <= Thread.MAX_PRIORITY) {
thread = new Thread(runnable);
} else {
try {
thread = RTThreadAssigner.assignRTThread(priority, runnable);
} catch(LinkageError e) {}
if(thread == null) {
priority = Thread.MAX_PRIORITY;
thread = new Thread(runnable);
}
}
thread.setPriority(priority);
return thread;
}
}
class RTThreadAssigner {
static Thread assignRTThread(int priority, Runnable runnable) {
Scheduler defScheduler = Scheduler.getDefaultScheduler();
PriorityScheduler scheduler = (PriorityScheduler) defScheduler;
if(priority >= scheduler.getMinPriority()) {
return new RealtimeThread(
null, null, null, null, null, runnable);
}
return null;
}
}
清单 1 中的代码必须与 RTSJ 类一起编译。在运行时,如果未找到实时类,代码将捕获虚拟机抛出的 LinkageError 并实例化常规 Java 线程来代替实时线程。这允许代码在任何虚拟机(无论是否实时虚拟机)上运行。
在清单 1 中,提供 RealtimeThread 对象的方法被指定为自身的一个类。通过这种方式,只有在加载该类时才会验证该方法,这在第一次访问 assignRTThread 方法时完成。当加载类时,运行时虚拟机字节码验证器尝试验证 RealtimeThread 类是否为 Thread 类的子类,如果为找到实时类,验证将失败并抛出 NoClassDefFoundError。
使用反射分配线程
清单 2 演示了一种替代技术,它具有的效果与清单 1 相同。它首先设置一个优先级值来确定期望的线程类型,根据类名称实例化实时线程或常规线程。这段反射式代码要求类中存在一个构造函数,这个函数接受一个 java.lang.Runnable 实例作为最后的参数,为所有其他参数传递空值。
清单 2. 使用反射来分配线程
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class ThreadLogic implements Runnable {
static void startThread(int priority) {
Thread thread = ThreadAssigner.assignThread(
priority, new ThreadLogic());
thread.start();
}
public void run() {
System.out.println("Running " + Thread.currentThread());
}
}
class ThreadAssigner {
static Thread assignThread(int priority, Runnable runnable) {
Thread thread = null;
try {
thread = assignThread(priority <= Thread.MAX_PRIORITY, runnable);
} catch(InvocationTargetException e) {
} catch(IllegalAccessException e) {
} catch(InstantiationException e) {
} catch(ClassNotFoundException e) {
}
if(thread == null) {
thread = new Thread(runnable);
priority = Math.min(priority, Thread.MAX_PRIORITY);
}
thread.setPriority(priority);
return thread;
}
static Thread assignThread(boolean regular, Runnable runnable)
throws InvocationTargetException, IllegalAccessException,
InstantiationException, ClassNotFoundException {
Thread thread = assignThread(
regular ? "java.lang.Thread" :
"javax.realtime.RealtimeThread", runnable);
return thread;
}
static Thread assignThread(String className, Runnable runnable)
throws InvocationTargetException, IllegalAccessException,
InstantiationException, ClassNotFoundException {
Class clazz = Class.forName(className);
Constructor selectedConstructor = null;
Constructor constructors[] = clazz.getConstructors();
top:
for(Constructor constructor : constructors) {
Class parameterTypes[] =
constructor.getParameterTypes();
int parameterTypesLength = parameterTypes.length;
if(parameterTypesLength == 0) {
continue;
}
Class lastParameter =
parameterTypes[parameterTypesLength - 1];
if(lastParameter.equals(Runnable.class)) {
for(Class parameter : parameterTypes) {
if(parameter.isPrimitive()) {
continue top;
}
}
if(selectedConstructor == null ||
selectedConstructor.getParameterTypes().length
> parameterTypesLength) {
selectedConstructor = constructor;
}
}
}
if(selectedConstructor == null) {
throw new InstantiationException(
"no compatible constructor");
}
Class parameterTypes[] =
selectedConstructor.getParameterTypes();
int parameterTypesLength = parameterTypes.length;
Object arguments[] = new Object[parameterTypesLength];
arguments[parameterTypesLength - 1] = runnable;
return (Thread) selectedConstructor.newInstance(arguments);
}
}
清单 2 中的代码无需与类路径上的实时类一起编译,因为实时线程使用 Java 反射来实例化。
根据类继承来分配线程
下一个示例演示了如何更改给定类的继承关系来利用实时线程。可以创建给定线程类的两个版本,一个版本可以感知 javax.realtime.RealtimeThread,而另一个不能。您的选择取决于底层的 JVM。只需在您的分发版中包含相关类文件,就可以启用对应版本。无论选择哪个版本,代码都相对较简单并且可避免任何异常处理,这与前面的例子不同。但是,当分发应用程序时,必须包含两个类中的一个,具体选择取决于将运行应用程序的相关虚拟机。
清单 3 中的代码采用标准方式创建常规 Java 线程:
清单 3. 使用类继承来分配线程
import javax.realtime.PriorityScheduler;
import javax.realtime.RealtimeThread;
import javax.realtime.Scheduler;
public class ThreadLogic implements Runnable {
static void startThread(int priority) {
ThreadContainerBase base = new ThreadContainer(priority, new ThreadLogic());
Thread thread = base.thread;
thread.start();
}
public void run() {
System.out.println("Running " + Thread.currentThread());
}
}
class ThreadContainer extends ThreadContainerBase {
ThreadContainer(int priority, Runnable runnable) {
super(new Thread(runnable));
if(priority > Thread.MAX_PRIORITY) {
priority = Thread.MAX_PRIORITY;
}
thread.setPriority(priority);
}
}
class ThreadContainerBase {
final Thread thread;
ThreadContainerBase(Thread thread) {
this.thread = thread;
}
}
要启用实时线程,可以更改 ThreadContainer 代码,如清单 4 所示:
清单 4. 一种用于启用实时线程的替代线程容器类
class ThreadContainer extends ThreadContainerBase {
ThreadContainer(int priority, Runnable runnable) {
super(assignRTThread(priority, runnable));
thread.setPriority(priority);
}
static Thread assignRTThread(int priority, Runnable runnable) {
Scheduler defScheduler = Scheduler.getDefaultScheduler();
PriorityScheduler scheduler = (PriorityScheduler) defScheduler;
if(priority >= scheduler.getMinPriority()) {
return new RealtimeThread(
null, null, null, null, null, runnable);
}
return new Thread(runnable);
}
}
在实时 JVM 中运行应用程序时,可以在其中包含这个新编译的 ThreadContainer 类来替代旧的类。
隔离的内存区域
所有 JVM(包括实时 JVM)都包含经过垃圾收集的堆。JVM 通过垃圾收集从堆回收内存。实时 JVM 拥有的垃圾收集算法旨在避免或最小化对正在运行的应用程序的干扰。
RTSJ 为每个线程引入了分配上下文 的概念。当将一个内存区域用作一个线程的分配上下文时,该线程实例化的所有对象都从该区域分配。RTSJ 指定以下附加的隔离内存区域:
单独的堆 内存区域。
单独的永远空闲 内存区域,其中的内存永远不会被使用。当运行静态初始化器时,初始化类的线程使用此区域作为分配上下文。尽管永远空闲的内存不需要垃圾收集器的注意,但对它的使用没有任何限制,因为其中的内存不会被回收。
范围 内存区域(范围)。范围无需任何垃圾收集活动,它们的内存也可以一次性地完整回收,以供重用。当虚拟机决定不再将一个范围当作任何活动线程的分配上下文区域时,在该范围中分配的对象就会被终结和清除,从而释放分配给它们的内存以供重用。
物理 内存区域根据类型或地址来确定。可以指定将每个物理内存区域当作范围区域重用,或者当作永远空闲区域供一次性使用。这类内存区域可以访问具有特定特征的内存,或从特定设备(比如闪存或共享内存)进行访问。
范围概念对对象引用引入了更强的限制。当释放了一个范围内存块时,其中的对象将被清除,绝不能存在具有指向已释放内存块内部的引用的对象,否则将导致悬摆指针。这在一定程度上是通过实施分配规则来完成的。分配规则指明从非范围内存区域分配的对象不能指向内存区域。这能够确保当释放范围对象时,其他内存区域的对象不会保留对不存在的对象的引用。
图 1 展示了这些内存区域和分配规则:
图 1. 内存区域和对象引用的分配规则
分配规则不允许一个范围内的对象指向另一个范围。但是,这意味着每个对象必须有一个强制范围清除顺序,这个顺序由每个线程内部的一个堆栈维护。除范围以外,该堆栈还包括对进入其中的其他内存区域的引用。只要内存区域成为了线程的分配上下文,它就会被放在线程的范围堆栈的顶部。分配规则指明在堆栈较高位置的范围中的对象可以引用堆栈中较低位置的范围中的对象。因为在顶部的范围会首先被清除。较低位置的范围不允许引用较高位置的范围。
堆栈中的范围顺序与其他线程的堆栈中的范围顺序保持一致。一旦将一个范围放在任何线程的堆栈上,该堆栈中离其最近的范围被当作是它的父范围(如果堆栈中没有其他范围,则将唯一的原始范围 当作父范围)。尽管该范围仍然在堆栈上,但只有父范围保持一致时,才能将该范围放在任何其他线程的堆栈上,这意味着它是其他线程的堆栈中位置最高的范围。换句话说,被使用的范围只能有一个父对象。这可以确保当释放范围时,会按相同顺序进行清除,无论哪个线程执行每个范围的清除任务,并且分配规则会在所有线程中保持一致。
如何利用隔离内存区域
可以通过两种方式来使用特定内存区域:将该区域指定为运行线程的初始内存区域(在构造线程对象时指定),或者显式地输入该区域,为其提供一个将执行的 Runnable 对象(将该区域指定为默认区域)。
但使用不同的内存区域时,必须考虑一些特殊的因素,因为这些因素会带来复杂性和可能的风险。必须选择区域的大小和数量。如果范围正在被使用,必须谨慎设计线程的范围堆栈的顺序,还必须考虑分配规则。
调度时间敏感型代码的选项
当使用内存区域而不是堆时,可以选择使用 javax.realtime.NoHeapRealtimeThread (NHRT),这是 javax.realtime.RealtimeThread 的一个子类,它实现的对象可以在不受到垃圾收集器干扰的情况下运行。它们可以在不受干扰的情况下运行,因为它们不能访问从堆分配的任何对象。任何违反此访问限制的尝试都会抛出一个 javax.realtime.MemoryAccessError。
另一个调度选项是异步事件处理器,可以使用它来调度将执行的代码,以响应异步或定期事件。(如果事件是由定时器发起的,那么它们可能是定期的)。这使您无需为这类事件显式地调度线程。相反。虚拟机维护一个共享线程池,该线程池被分派用于在发生事件时运行异步事件处理器的代码。这可以简化实时应用程序,将您从对线程和内存区域的管理中解放出来。
图 2 中的类图显示了可用于调度代码的选项:
图 2. 此类图演示了调度代码的选项
图 3 展示了如何分派异步事件处理其:
图 3. 异步事件处理器分派方式
一般而言,可移植性和模块化有助于将响应事件的代码与启用和分派处理器的代码分离开来。当将代码封装到 java.lang.Runnable 实现中之后,可以通过许多选项来分派该代码。可以选择构造一个线程来执行代码,或者使用异步事件处理器(利用线程池)按需执行代码,或者将两者结合使用。
表 1 比较了各种可能选择的特征:
表 1. 对比在实时 Java 中分派代码的各种方法
共享执行代码的线程 | 可定期分派 | 可在堆内存中运行 | 可在永远空闲内存中运行 | 可在范围内存中运行 | 可为其分配一个期限 | 将在不受垃圾收集干扰的情况下运行 | |
常规 Thread | 否 | 否 | 是 | 是 | 否 | 否 | 否 |
RealtimeThread | 否 | 是 | 是 | 是 | 是 | 是 | 否 |
NoHeapRealtimeThread | 否 | 是 | 否 | 是 | 是 | 是 | 是 |
AsyncEventHandler | 是 | 是,当附加到周期计时器时 | 是 | 是 | 是 | 是 | 否 |
BoundAsyncEventHandler | 否 | 是,当附加到周期计时器时 | 是 | 是 | 是 | 是 | 否 |
无堆 AsyncEventHandler | 是 | 是,当附加到周期计时器时 | 否 | 是 | 是 | 是 | 是 |
无堆 BoundAsyncEventHandler | 否 | 是,当附加到周期计时器时 | 否 | 是 | 是 | 是 | 是 |
当考虑使用哪些调度选项和内存区域时,会遇到实时 Java 独有的一些设计问题。一般而言,为实时环境编程比编写直观的传统应用程序更具挑战性,并且实时 Java 自身也具有一些挑战性。表 2 列出了当使用附加内存区域、NHRT 和其他实时特性时可能带来的一些复杂性:
表 2. 实时线程和内存区域的一些复杂性和难题
考虑因素 | 细节 |
分配给内存区域的内存 | 应用程序创建的每个内存区域会被分配所申请的内存大小。选择的大小太大会降低内存使用效率,但是选择的大小太小很容易使应用程序遇到 OutOfMemoryError。在开发期间,即使应用程序没有变化,底层库也可能改变。这可能导致意外的附加内存使用,导致内存使用超出内存区域限制。 |
共享范围的计时因素 | 多个线程共享的范围内存区域的无需很大,因为在没有内存使用它时会将它清除。但是,对使用范围的线程计时稍作更改之后,范围就会始终被用作线程的分配上下文。这将导致该范围始终不会被清除,导致 OutOfMemoryError。 当进入和清除共享的范围区域时,线程之间可能发生临时的锁争用。 |
运行时异常 IllegalAssignmentError、MemoryAccessError 和 IllegalThreadStateException | 如果未足够重视代码设计,就可能发生这些异常。实际上,对程序行为和计时的细微更改可能导致这些异常的意外出现。一些示例包括:
由于线程之间的计时和同步变化,堆中在正常情况下不可被 NHRT 使用的对象可能变得可用。 当不知道从哪个内存区域分配对象或者特定范围位于范围堆栈上的何处时,可能发生 IllegalAssignmentError。 当进入范围内存区域的代码由常规线程运行时,将抛出 IllegalThreadStateException。 由于分配规则的限制,通常使用静态字段或其他缓存数据方式的代码会使范围不安全,可能导致 IllegalAssignmentError。 |
类初始化 | 任何类型的常规或实时线程都可以初始化类,包括 NHRT(它可能导致意外的 MemoryAccessError)。 |
使用 finalize 方法终结对象 | 退出范围的最后一个线程用于终结范围中的所有对象:
如果 finalize 方法创建线程,则范围可能不能如期被清除。 终结也可能导致死锁。在终结内存区域之前,终结线程可能获取了锁。其他线程可能会争用这些锁和在终结期间将获取的锁,进而导致死锁。 |
意外 NHRT 延迟 | 尽管可以保证 NHRT 的运行不会受到垃圾收集的直接干扰,但 NHRT 可能与由垃圾收集抢占的其他线程类型共享相同的锁。如果 NHRT 在尝试获取这类锁时被延迟了,而且拥有该锁的线程被垃圾收集延迟了,那么垃圾收集也会间接地延迟 NHRT。 |
综合示例
下一个示例演示了到目前为止介绍的一些实时特性。首先,清单 5 展示了两个类,它们分别是事件数据的生成者和使用者。两个类都是 Runnable 的实现,所以它们可以由任何给定 Schedulable 对象轻松执行。
清单 5. 事件对象的生成者和使用者类
class Producer implements Runnable {
volatile int eventIdentifier;
final Thread listener;
Producer(Thread listener) {
this.listener = listener;
}
public void run() {
LinkedList<Integer> events = getEvents();
synchronized(listener) {
listener.notify();
events.add(++eventIdentifier); //autoboxing creates an Integer object here
}
}
static LinkedList<Integer> getEvents() {
ScopedMemory memoryArea = (ScopedMemory) RealtimeThread.getCurrentMemoryArea();
LinkedList<Integer> events =
(LinkedList<Integer>) memoryArea.getPortal();
if(events == null) {
synchronized(memoryArea) {
if(events == null) {
events = new LinkedList<Integer>();
memoryArea.setPortal(events);
}
}
}
return events;
}
}
class Consumer implements Runnable {
boolean setConsuming = true;
volatile boolean isConsuming;
public void run() {
Thread currentThread = Thread.currentThread();
isConsuming = true;
try {
LinkedList<Integer> events = Producer.getEvents();
int lastEventConsumed = 0;
synchronized(currentThread) {
while(setConsuming) {
while(lastEventConsumed < events.size()) {
System.out.print(events.get(lastEventConsumed++) + " ");
}
currentThread.wait();
}
}
} catch(InterruptedException e) {
} finally {
isConsuming = false;
}
}
}
在 清单 5 中,生成者和使用者对象访问一个事件队列,这些事件被编码为一系列 java.lang.Integer 对象。代码期望当前的分配上下文为一个范围内存区域,期望事件队列存储为范围的门户对象。(门户是从范围分配的对象,可以存储在范围内存区域对象自身之中。它非常方便有用,因为范围对象既不能存储在静态字段中,也不能存储在从父范围分配的对象中)。如果未找到队列,将创建该队列。使用两个可变字段来向所关注线程告知事件的生成和使用进度。
清单 6 中的两个类展示了如何执行 清单 5 中的代码:
清单 6. 可调度的类
class NoHeapHandler extends AsyncEventHandler {
final MemoryArea sharedArea;
final Producer producer;
NoHeapHandler(
PriorityScheduler scheduler,
ScopedMemory sharedArea,
Producer producer) {
super(new PriorityParameters(scheduler.getMaxPriority()),
null, null, null, null, true);
this.sharedArea = sharedArea;
this.producer = producer;
}
public void handleAsyncEvent() {
sharedArea.enter(producer);
}
}
class NoHeapThread extends NoHeapRealtimeThread {
boolean terminate;
final MemoryArea sharedArea;
final Consumer consumer;
NoHeapThread(
PriorityScheduler scheduler,
ScopedMemory sharedArea,
Consumer consumer) {
super(new PriorityParameters(scheduler.getNormPriority()),
RealtimeThread.getCurrentMemoryArea());
this.sharedArea = sharedArea;
this.consumer = consumer;
}
public synchronized void run() {
try {
while(true) {
if(consumer.setConsuming) {
sharedArea.enter(consumer);
} else {
synchronized(this) {
if(!terminate) {
if(!consumer.setConsuming) {
wait();
}
} else {
break;
}
}
}
}
} catch(InterruptedException e) {}
}
}
在清单 6 中,数据生成代码被分配给一个异步事件处理器,以在最高的可用优先级上运行。该处理器进入一个范围内存区域来运行数据生成代码。相同的范围内存区域是 NHRT 类的一个参数,充当数据的使用者。线程类也很直观,允许异步访问 terminate 和 setConsuming 字段来指示行为。当使用者字段使用事件时,它会进入共享内存区域来执行使用者代码(在比生成者更低的优先级上运行)。(示例中的使用行为很简单,只是将事件标识符输出到控制台)。
清单 7 给出了初始化系统并展示系统行为的代码:
清单 7. 系统行为
public class EventSystem implements Runnable {
public static void main(String args[]) throws InterruptedException {
RealtimeThread systemThread = new RealtimeThread(
null, null, null, new VTMemory(20000L), null, null) {
public void run() {
VTMemory systemArea = new VTMemory(20000L, new EventSystem());
systemArea.enter();
}
};
systemThread.start();
}
public void run() {
try {
PriorityScheduler scheduler =
(PriorityScheduler) Scheduler.getDefaultScheduler();
VTMemory scopedArea = new VTMemory(20000L);
Consumer consumer = new Consumer();
NoHeapThread thread = new NoHeapThread(scheduler, scopedArea, consumer);
Producer producer = new Producer(thread);
NoHeapHandler handler = new NoHeapHandler(scheduler, scopedArea, producer);
AsyncEvent event = new AsyncEvent();
event.addHandler(handler);
int handlerPriority =
((PriorityParameters) handler.getSchedulingParameters()).getPriority();
RealtimeThread.currentRealtimeThread().setPriority(handlerPriority - 1);
thread.start();
waitForConsumer(consumer);
//fire several events while there is a consumer
event.fire();
event.fire();
event.fire();
waitForEvent(producer, 3);
setConsuming(thread, false);
//fire a couple of events while there is no consumer
event.fire();
event.fire();
waitForEvent(producer, 5);
setConsuming(thread, true);
waitForConsumer(consumer);
//fire another event while there is a consumer
event.fire();
waitForEvent(producer, 6);
synchronized(thread) {
thread.terminate = true;
setConsuming(thread, false);
}
} catch(InterruptedException e) {}
}
private void setConsuming(NoHeapThread thread, boolean enabled) {
synchronized(thread) {
thread.consumer.setConsuming = enabled;
thread.notify();
}
}
private void waitForEvent(Producer producer, int eventNumber)
throws InterruptedException {
while(producer.eventIdentifier < eventNumber) {
Thread.sleep(100);
}
}
private void waitForConsumer(Consumer consumer)
throws InterruptedException {
while(!consumer.isConsuming) {
Thread.sleep(100);
}
}
}
在 清单 7 中,两个范围被用作非堆线程和处理器的范围堆栈,使用两个范围是因为这些 Schedulable 不能访问由堆分配的任何对象。异步事件对象代表事件,具有在触发事件时为其分派的附加处理器。初始化系统之后,代码启动使用者线程并多次触发事件,在比事件处理器更低的优先级上运行。这段代码还会在触发附加线程时打开和关闭使用者线程。
清单 8 给出了在实时 JVM 中运行 EventSystem 时的输出:
清单 8. 控制台输出
1 2 3 6
本示例有一个有趣之处,那就是为什么没有报告事件 4 和 5。监听线程每次报告队列中的事件时,它会从队列前端开始,到队列末尾结束,这意味着所有 6 个事件至少被报告一次。
但是,代码的设计可确保用于存储事件的内存在没有被任何线程使用时将自动被释放。当使用者线程停止读取队列时,它将退出范围内存区域,这时没有任何 Schedulable 对象将该区域用作分配上下文。
使用区域的 Schedulable 对象的缺少意味着范围区域中的对象已被清除,范围区域已被重置。这包括门户对象,所以当线程停止监听时,队列和其中的所有事件都会被释放。每次触发后续事件时,都会重新创建和填充队列,但是如果没有监听线程,内存就会被立即释放。
内存管理是自动执行的,它的运行不会受到垃圾收集器的干扰,无论收集器是否是活动的(因为处理器和线程都与堆没有关系)。各个事件在内存中存储为一个对象队列,如果一个监听线程可以使用它们,那么这个队列会继续增长。如果没有这样的监听线程,队列和相关的事件将自动被释放。
一般使用场景
借助调度和内存管理框架,可以设计一个具有各种优先级线程的应用程序,以在实时虚拟机中最佳地执行(并且也可能在其他虚拟机中很好地运行)。应用程序可以包含具有高优先级的事件处理线程,从外部输入收集数据并存储数据以供处理。由于这些事件处理线程的过渡性和异步特性,它们可能也适用于其他内存管理机制,它们可能具有极严格的实时约束。在中间优先级上,可能存在使用数据和执行计算或分发数据的处理线程。中间线程可能需要分配足够的 CPU 利用率来管理其工作负载。在最低优先级上,可能存在维护和日志记录线程。如果使用实时虚拟机来管理应用程序中各种任务的调度和内存使用,则可以让内存最高效地运行。
RTSJ 的目的是使开发人员可以编写能够在必需的实时约束下运行的应用程序。使用实时调度器和线程就足以实现此目标了。如果还不能实现,可能需要执行更高级的开发,利用由虚拟机实现的一个或多个更高级的特性。
结束语
本文介绍了一些技巧,您可以利用它们将实时 Java 元素集成到 Java 应用程序中。本文介绍了一些调度和内存管理特性,您可能希望利用它们来实现实时性能。本文只是您利用 Java 语言的传统优势(比如互操作性和安全性)的开端,将这些优势与新的特性组合在一起,就可以满足应用程序所需的实时约束。
在本系列的下一期中,您将了解将现有应用程序移植到实时 Java 的技巧。最后一期将以前两期为基础,逐步演示操作实时 Java 的实时系统的设计、验证和调试。
本文示例源代码或素材下载
更多精彩
赞助商链接