實時 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 的實時系統的設計、驗證和調試。
本文配套源碼