实时Java是Java语言的一组增强功能,它们为应用程序提供的实时性能要超过标准Java技术。 实时性能与传统的吞吐量性能不同,传统的吞吐量性能通常是对可以在固定时间内完成的指令,任务或工作总数的度量。 实时性能侧重于应用程序需要在不超过给定时间限制的情况下响应外部刺激的时间。 在硬实时系统中,绝不能超过这种限制; 软实时系统对冲突的容忍度更高。 实时性能要求应用程序本身获得对处理器的控制,以便它可以对刺激做出响应,并且在响应刺激时,虚拟机内的竞争进程不会阻止应用程序的代码执行。 实时Java提供了Java应用程序以前无法满足的响应能力。
实时JVM可以利用实时操作系统(RTOS)服务来提供硬实时功能,或者可以在更常规的操作系统上运行,以用于具有较弱实时约束的应用程序。 当您切换为使用实时JVM时,实时Java中使用的某些技术是“免费”的。 但是要利用实时Java的某些功能,需要对应用程序进行一些更改。 这些功能是本文的重点。
必须约束的子流程
JVM通过执行只能由应用程序严格控制的工作来为给定的应用程序提供服务。 JVM中有几个运行时子流程,包括:
- 垃圾回收 :这是回收应用程序丢弃的运行时内存块的工作。 垃圾回收可能会使应用程序的执行延迟一段时间。
- 类加载 :此过程之所以被称为是因为Java应用程序是按类的粒度加载的,因此涉及从文件系统或网络加载应用程序结构,指令和其他资源。 在标准Java中,应用程序在首次引用时会加载每个类( 延迟加载)。
- 即时(JIT)动态编译 :在应用程序运行时,许多虚拟机都使用从解释后的Java字节码到本机指令的动态编译方法。 尽管这可以提高性能,但是编译活动本身可能导致暂时的延迟,从而阻止了应用程序代码的运行。
- 调度 :在标准Java中,允许对应用程序进行最小程度的控制,以规定其自身正在运行的线程的调度以及与同一操作系统上运行的其他应用程序相关的应用程序的调度。
所有这些子流程都会阻碍应用程序对外部刺激的响应,因为它们会延迟应用程序代码的执行。 例如,可以调度指令序列以响应于来自网络,雷达系统,键盘或任何其他设备的信号而执行。 实时应用程序具有最小可接受的时间段,在该时间段内允许无关的进程(例如垃圾回收)延迟指令的响应序列的执行。
实时Java提供了各种技术,旨在最大程度地减少这些底层子进程对应用程序的干扰。 切换到实时JVM时出现的“免费”技术包括专门的垃圾收集,该垃圾收集限制了收集中断的时间和影响,专门的类加载允许在启动时优化性能,而不是延迟优化,专门的锁定和同步,以及专门的优先级线程调度(避免优先级反转)。 但是,需要对应用程序进行一些修改,尤其是要利用Java实时规范(RTSJ)引入的功能。
RTSJ提供了一个API,可在JVM中启用众多实时功能。 这些功能中的某些功能在规范的实现中是必需的,其他功能是可选的。 该规范涵盖以下方面的一般领域:
- 实时调度
- 高级内存管理
- 高分辨率计时器
- 异步事件处理
- 线程异步中断
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
类的子类,如果未找到RealtimeThread
类,则该类将失败,并出现NoClassDefFoundError
。
使用反射分配线程
清单2演示了一种与清单1效果相同的替代技术。它从优先级值开始确定所需的线程类型,然后根据类名实例化实时或常规线程。 反射代码期望类中存在构造函数,该构造函数将java.lang.Runnable
的实例作为最后一个参数,并为所有其他参数传递null值。
清单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中的代码不需要使用classpath上的实时类进行编译,因为实时线程是使用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);
}
}
您可以在应用程序中包含此新编译的ThreadContainer
类文件,而不是在使用实时JVM运行该文件时将其包含在旧文件中。
隔离的内存区域
所有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 。 在开发过程中,即使应用程序没有更改,底层库也可以更改。 这可能会导致意外的额外内存使用,从而导致超出内存区域限制。 |
共享范围的时序注意事项 | 由多个线程共享的作用域内存区域可能看起来具有足够的大小,因为预计在没有线程使用该内存区域时将其清除。 但是,随着使用范围的线程时序发生细微变化,可能永远不会有范围不被用作任何线程的分配上下文的情况。 这导致它永远不会被清除的意外可能性,从而导致 输入和清除共享作用域区域时,线程之间可能会发生临时锁争用。 |
运行时异常IllegalAssignmentError , MemoryAccessError 和IllegalThreadStateException | 如果对代码设计的关注不足,可能会导致这些异常。 实际上,程序行为和时间的细微变化可能导致它们意外出现。 一些例子是:
|
类初始化 | 任何类型的常规或实时线程都可以初始化一个类,包括NHRT,这可能会导致意外的MemoryAccessError 。 |
使用finalize 方法finalize 对象 | 退出作用域的最后一个线程用于最终确定其中的所有对象:
|
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显示了EventSystem
在实时JVM中运行时的输出:
清单8.控制台输出
1 2 3 6
此示例的一个有趣方面是未报告事件4和5的原因。 每次侦听线程报告队列中的事件时,它都从队列的开头开始,一直到结尾,这表明所有六个事件将至少报告一次。
但是,该设计确保当没有线程使用事件时,用于存储事件的内存会自动被丢弃。 当使用者线程停止从队列中读取时,它将退出作用域内存区域,此时没有可Schedulable
对象使用该区域作为分配上下文。
如果没有使用该区域的可Schedulable
对象,则意味着清除了作用域区域中的对象并进行了重置。 这包括门户对象,因此当线程停止侦听时,队列和队列中的所有事件都将被丢弃。 每次触发后续事件时,都会重新创建并重新填充队列,但是如果没有侦听线程,则此后立即丢弃内存。
如果内存管理器处于活动状态,则内存管理是自动的,并且不受垃圾回收器的干扰而运行(因为处理程序和线程都是无堆的)。 事件作为对象队列存储在内存中,如果有监听线程可以使用它们,则事件将继续增长。 如果不是,队列和关联的事件将被自动丢弃。
一般使用场景
使用调度和内存管理框架,您可以为线程设计具有各种优先级的应用程序,以便在实时虚拟机中(并可能在其他虚拟机中)充分发挥性能。 该应用程序可能包括高优先级的事件处理线程,从外部输入收集数据并存储数据以进行处理。 由于它们的瞬时和异步特性,这些事件处理线程可能适用于备用内存管理,并且它们可能最受实时限制。 在中等优先级级别,可能会有处理线程消耗数据并进行计算或分发数据。 中间线程可能需要适当分配的CPU利用率来管理其工作负载。 在最低优先级下,可能会有维护和日志记录线程。 使用实时虚拟机来管理应用程序中这些各种任务的调度和内存使用情况可使它最有效地运行。
RTSJ的目的是使开发人员能够编写在所需的实时约束下运行的应用程序。 只需使用实时调度程序和线程就足以实现该目标。 如果不是这样,则可能需要进行更高级的开发才能利用虚拟机实现的一项或多项高级功能。
第1部分的结论
本文概述了一些技巧,可帮助您开始将实时Java的元素集成到Java应用程序中。 它涵盖了您可能希望用于实现实时性能的一些调度和内存管理功能。 这是您利用Java语言的传统优势(例如互操作性和安全性)并将它们与新功能结合在一起的起点,从而使您能够满足应用程序所需的实时约束。
在本系列的下一部分中,您将学习将现有应用程序移植到实时Java的技术。 最后一篇文章将基于前两部分,并带您设计,验证和调试包含实时Java的实时系统。
翻译自: https://www.ibm.com/developerworks/java/library/j-devrtj1/index.html