RT 线程是 javax.realtime.RealtimeThread 的一个实例。RTSJ 要求规范的实现必须为 RT 线程提供至少 28 个连续的优先级。这些优先级被称作实时优先级。规范中并没有指定 RT 优先级范围的开始值,除非其优先级高于 10 —— 普通 Java 线程的最高优先级值。出于可移植性的原因,应用程序代码应使用新的 PriorityScheduler 类的 getPriorityMin() 和 getPriorityMax() 方法来确定可用的 RT 优先级值的范围。
对 RT 线程的推动
JLS 中的线程调度并不精确而且只提供了 10 个优先级值。由 Linux 实现的 POSIX SCHED_OTHER 策略满足了各种应用程序的需要。但是 SCHED_OTHER 策略具有一些不好的特性。动态优先级调整和时间片划分可能在不可预测的时间内发生。SCHED_OTHER 优先级的值(40)其实并不算大,其中一部分已经被使用普通 Java 线程的应用程序和动态优先级调整利用了。JVM 还需要对内部线程使用优先级以达到一些特殊目的,比如垃圾收集(GC)。
缺少确定性、需要更高的优先级级别以及要求与现有应用程序兼容,这些因素引发了对扩展的需求,这将为 Java 程序员提供新的调度功能。RTSJ 中描述的 javax.realtime 包中的类提供了这些功能。在 WebSphere Real Time 中,Linux SCHED_FIFO 调度策略满足了 RTSJ 调度需求。
RT Java 线程的线程调度
在 WebSphere Real Time 中,支持 28 个 RT Java 优先级,其范围为 11 到 38。PriorityScheduler 类的 API 应用于检索这个范围。本节描述了比 RTSJ 更多的线程调度细节以及 Linux SCHED_FIFO 策略的一些方面,已经超出了 RTSJ 的需求。
RTSJ 将 RT 优先级视作由运行时系统在逻辑上实现的优先级,该系统为每个 RT 优先级保持一个独立队列。线程调度程序必须从非空的最高优先级队列的头部开始调度。注意:如果所有队列中的线程都不具有 RT 优先级,则调度一个普通 Java 线程按 JLS 中的描述执行
具有 RT 优先级的调度线程可以一直执行直至阻塞,通过让步自愿放弃控制权,或被具有更高 RT 优先级的线程抢占。具有 RT 优先级并自愿让步的线程的优先级被置于队列的后端。RTSJ 还要求此类调度在不变的时间内进行,并且不能随某些因素变化(如当前执行的 RT 线程的数量)。RTSJ 的 1.02 版本对单处理器系统应用了这些规则;RTSJ 对于多处理器系统上的调度如何运作未作要求。
Linux 为所有适当的 RTSJ 调度需求提供了 SCHED_FIFO 策略。SCHED_FIFO 策略用于 RT 而不用于用户任务。SCHED_FIFO 与 SCHED_OTHER 策略的区别在于前者提供了 99 个优先级级别。SCHED_FIFO 不为线程分时间片。同样,SCHED_FIFO 策略也不动态调整 RT 线程的优先级,除非通过优先级继承锁定策略。由于优先级继承的原因,RTSJ 需要使用优先级调整。
Linux 为 RT 线程和普通 Java 线程提供不变时间调度。在多处理器系统中,Linux 试图模拟分派到可用处理器的单个全局 RT 线程队列的行为。这与 RTSJ 的精神最为接近,但确实与用于普通 Java 线程的 SCHED_OTHER 策略不同。
使用 RT 线程的有问题的代码示例
清单 2 修改 清单 1 中的代码来创建 RT 线程而不是普通 Java 线程。使用 java.realtime.RealtimeThread 而不是 java.lang.Thread 指出了其中的区别。第一个线程创建于第 4 RT 优先级而第二个线程创建于第 6 RT 优先级,与 getPriorityMin() 方法确定的相同。
清单 2. RT 线程
2 class myRealtimeThreadClass extends javax.realtime.RealtimeThread {
3 volatile static boolean Stop = false;
4
5 // Primordial thread executes main()
6 public static void main(String args[]) throws InterruptedException {
7
8 // Create and start 2 threads
9 myRealtimeThreadClass thread1 = new myRealtimeThreadClass();
10 // want 1st thread at 4th real-time priority
11 thread1.setPriority(PriorityScheduler.getMinPriority(null)+ 4);
12 myRealtimeThreadClass thread2 = new myRealtimeThreadClass();
13 // want 2nd thread at 6th real-time priority
14 thread2.setPriority(PriorityScheduler.getMinPriority(null)+ 6);
15 thread1.start(); // start 1st thread to execute run()
16 thread2.start(); // start 2nd thread to execute run()
17
18 // Sleep for 5 seconds, then tell the threads to terminate
19 Thread.sleep(5*1000);
20 Stop = true;
21 }
22
23 public void run() { // Created threads execute this method
24 System.out.println("Created thread");
25 int count = 0;
26 for (;Stop != true;) { // continue until asked to stop
27 count++;
28 // Thread.yield(); // yield to other thread
29 }
30 System.out.println("Thread terminates. Loop count is " + count);
31 }
32 }
33
清单 2 中修改后的代码存在一些问题。如果程序在单处理器环境中运行,则它永远不会结束并且只能打印以下内容:
2
出现这样的结果可以用 RT 线程调度的行为来解释。原始线程仍然是一个普通 Java 线程并利用非 RT(SCHED_OTHER)策略运行。只要原始线程启动第一个 RT 线程,RT 线程就抢占原始线程并且 RT 线程将会不确定地运行,因为它不受时间量和线程阻塞的限制。原始线程被抢占后,就再也不允许执行,因此再也不会启动第二个 RT 线程。Thread.yield() 对允许原始线程执行反而不起作用 —— 因为让步逻辑将 RT 线程置于其运行队列的末端 —— 但是线程调度程序将再次调度这个线程,因为它是运行队列前端的具有最高优先级的线程。
该程序在双处理器系统中同样会失败。它将打印以下内容:
2 Created thread
3
允许使用原始线程创建这两个 RT 线程。但是创建第二个线程后,原始线程被抢占并且再也不允许告知线程结束,因为两个 RT 线程在两个处理器上执行而且永远不会阻塞。
在带有三个或更多处理器的系统上,程序运行至完成并生成一个结果。
单处理器上运行的 RT 代码示例
清单 3 显示了修改后能在单处理器系统中正确运行的代码。main() 方法的逻辑被移到了一个具有第 8 RT 优先级的 “main” RT 线程中。这个优先级比主 RT 线程创建的两个其他 RT 线程的优先级都要高。拥有最高的 RT 优先级使这个主 RT 线程能够成功地创建两个 RT 线程,并且还允许它从五秒钟的休眠中苏醒时能够抢占当前运行的线程。
清单 3. 修改后的 RT 线程示例
2 import javax.realtime.*;
3 class myRealtimeThreadClass extends javax.realtime.RealtimeThread {
4 volatile static boolean Stop = false;
5
6 static class myRealtimeStartup extends javax.realtime.RealtimeThread {
7
8 public void run() {
9 // Create and start 2 threads
10 myRealtimeThreadClass thread1 = new myRealtimeThreadClass();
11 // want 1st thread at 4th real-time priority
12 thread1.setPriority(PriorityScheduler.getMinPriority(null)+ 4);
13 myRealtimeThreadClass thread2 = new myRealtimeThreadClass();
14 // want 1st thread at 6th real-time priority
15 thread2.setPriority(PriorityScheduler.getMinPriority(null)+ 6);
16 thread1.start(); // start 1st thread to execute run()
17 thread2.start(); // start 2nd thread to execute run()
18
19 // Sleep for 5 seconds, then tell the threads to terminate
20 try {
21 Thread.sleep(5*1000);
22 } catch (InterruptedException e) {
23 }
24 myRealtimeThreadClass.Stop = true;
25 }
26 }
27
28 // Primordial thread creates real-time startup thread
29 public static void main(String args[]) {
30 myRealtimeStartup startThr = new myRealtimeStartup();
31 startThr.setPriority(PriorityScheduler.getMinPriority(null)+ 8);
32 startThr.start();
33 }
34
35 public void run() { // Created threads execute this method
36 System.out.println("Created thread");
37 int count = 0;
38 for (;Stop != true;) { // continue until asked to stop
39 count++;
40 // Thread.yield(); // yield to other thread
41 }
42 System.out.println("Thread terminates. Loop count is " + count);
43 }
44 }
45
当此程序在单处理器上运行时,它将打印以下结果:
2 Thread terminates. Loop count is 32767955
3 Created thread
4 Thread terminates. Loop count is 0
5
程序的输出显示所有的线程运行并结束,但是这两个线程只有一个执行 for 循环的一个迭代。这个输出可通过考虑 RT 线程的优先级来解释。主 RT 线程一直运行,直至调用 Thread.sleep() 方法来阻塞线程。主 RT 线程创建了两个 RT 线程,但是只有第二个 RT 线程(具有第 6 RT 优先级)才能够在主 RT 线程休眠时运行。这个线程一直运行,直至主 RT 线程从休眠中苏醒并指示线程结束。主 RT 线程一旦结束,就允许执行具有第 6 优先级的线程并结束。程序按这种方式执行并打印具有非零值循环计数。此线程结束后,就允许运行具有第 4 RT 优先级的线程,但它只是绕过 for 循环,因为系统指示结束该线程。该线程将打印零循环计数值然后结束。