Developing Real-Time Software with Java SE APIs: Part 1

by Kelvin Nilsen

Learn why Java SE is a good choice for implementing real-time systems, especially those that are large, complex, and dynamic.

Published August 2014

This article is Part 1 of a two-part series that describes some of the software engineering principles that serve as the foundation upon which modern real-time software systems are constructed. The presented methods and techniques have been proven in many successfully deployed Java SE applications, including a variety of telecommunications infrastructure devices; automation of manufacturing processes, ocean-based oil drilling rigs, and fossil fuel power plants; multiple radar systems; and the modernization of the US Navy's Aegis Warship Weapons Control System with enhanced ballistic missile defense capabilities.

Note: The full source code for the sample application described in this article is available here. Key excerpts of the code are presented and discussed in this article.

What Is Real-Time Software?

Unlike traditional business applications, real-time software is expected to deliver results under very stringent timing constraints. A computation that is performed at the wrong time—either too early or too late—is considered to be wrong, even if the computed numbers are exactly right. In the context of this article, the term real-time software means software that is engineered to comply with real-time constraints.

In other contexts, the term real-time software is occasionally used to describe software that is interactive with a human user. Real-time stock quotes, for example, might be delivered to your computer screen as soon as you type your inquiry. However, unless the provider of your stock quotes has taken precautions to guarantee that the quote appearing on your screen is no more than a certain number of milliseconds (ms) old, that use of the term is different from how the term is used in this article.

Engineers of real-time software often make a distinction between soft real-time systems and hard real-time systems. For purposes of this series, hard real-time systems follow a software engineering approach that requires all compliance with timing constraints to be validated prior to the execution of the software system. In other words, the engineer proves that the hard real-time application will satisfy all timing constraints by thoroughly analyzing the worst-case execution paths for every task, by analyzing the worst-case execution times for every machine instruction on each worst-case execution path (including delays resulting from cache misses and pipeline stalls associated with contention for processing resources), and by ensuring lower bounds on the time separation between consecutive firings of each task.

Systems that are engineered according to hard real-time discipline have no need to check at runtime whether deadlines are missed. However, hard real-time methodologies are extremely inefficient on most modern computing platforms. This is because it is very difficult to prove that moderately complex software will always benefit from the memory cache, speculative execution, and pipelining optimizations that are engineered into modern hardware platforms. So the very conservatively scheduled hard real-time system often ends up utilizing less than 5 percent of the hardware platform's commonly observed full-speed capacity.

Soft real-time systems, on the other hand, follow an approach under which execution times and task execution frequencies are analyzed more through empirical techniques (measurements and statistical characterizations), and the software itself is written to accommodate the possibility that timing constraints will occasionally be missed.

Java SE Versus Other Languages

The use of Java SE APIs in the implementation of real-time systems is most appropriate for soft real-time development. Using Java SE for hard real-time development is also possible, but generally requires the use of more specialized techniques such as the use of NoHeapRealtimeThread abstractions, as described in the Real-Time Specification for Java (JSR 1), or the use of the somewhat simpler ManagedSchedulable abstractions of the Safety Critical Java Technology specification (JSR 302).

It is also important to distinguish real-time engineering, as it is described in this series, from performance engineering. An e-commerce web server, for example, might have been carefully engineered to support an average of 1,000 transactions per second. That is different from saying that every transaction must be completed in 1 ms. It could be that some transactions require hundreds of ms and others are completed in less than 1 ms, as long as the average of all transactions is 1 ms. It could also mean that each transaction requires an average of 4 ms from start to end, but the system has the ability to concurrently execute four transactions at a time.

The benefits of the Java language are especially valuable in real-time applications that are large, complex, and dynamic. Software engineers are motivated to select Java SE when their projects require dynamic code updates, coordination between multiple teams of developers, integration of independently developed software components, support for multiple hardware or operating system platforms, or support for multiple software configurations as product requirements evolve over multiple years or even decades.

Projects that can be implemented entirely by one or two developers in a year's time are more likely to be implemented in a less powerful language such as C or C++, especially if it is critical that the end product minimize consumption of battery power or high-volume production costs. Such projects are less likely to appreciate the benefits of Java, and are often able to justify the higher software engineering and maintenance costs associated with the choice to use an older language.

Traction Monitoring Example Presented in This Article

As an example, this article looks at a modern automobile's traction monitoring system. Traction control makes a good study because it is simple enough to be presented in this article and, at the same time, it is complex enough to motivate mastery of the relevant principles of real-time software engineering.

The code for this sample application is contrived for the purposes of teaching the principles of real-time programming and does not represent the most appropriate implementation of a traction control system. Likewise, certain characteristics of the sensor architecture, such as the ability to read and report the rotational angle of a rapidly turning wheel to within four decimal digits of precision every 8 ms, are contrived for the purposes of simplifying the discussion.

Due to the extreme cost pressure on the components of a consumer vehicle, Java SE would probably not be the language of choice for a commercial implementation of traction control unless Java SE was being used to control multiple subsystems, thereby distributing its costs. When memory constraints are severe, more memory-efficient versions of Java can be used, such as Java ME or the draft JSR 302 standard (safety-critical Java).

The traction monitoring system, which is typically part of a much larger and more complex real-time system, detects slippage of tires during braking and acceleration. In a modern automobile, traction monitoring is often integrated with antilock braking, antislip acceleration, tire pressure monitoring, suspension control, transmission control, and even fuel injection and ignition control.

Our sample application focuses on detection of traction loss. We assume that the traction monitoring system has access to sensors that detect each of the following values:

  • One sensor for each of the vehicle's four wheels, representing the current rotational angle of the wheel. The sensed value is a floating point value representing degrees, measured in the forward rotational direction from straight up. For simplification, we assume the vehicle travels only in the forward direction.
  • The percentage of full possible depression of the accelerator pedal. The sensed value is a floating point number in the range of 0.0 to 1.0.
  • The percentage of full possible depression of the brake pedal. The sensed value is a floating point number in the range of 0.0 to 1.0.
  • The angle of turn indicated by the steering wheel. The sensed value is a floating point number in the range of -45.0 to 45.0, representing degrees. A negative value indicates the turn is toward the left.

We measure the speed and acceleration on each wheel independently by comparing the values produced by the rotational angle sensor for consecutive readings. Assume a wheel diameter of 24 inches. The circumference of the wheel is calculated as shown in Figure 1:

realtime-pt1-f1

Figure 1. Calculation of wheel circumference

Assume further that the operating constraints on the vehicle limit its speed to no more than 150 miles per hour. The dimensional analysis in Figure 2 shows that the minimum time for a wheel rotation is 28.56 ms:

realtime-pt1-f2

Figure2. Dimensional analysis

Given these assumptions, as long as we sample the wheel's rotational angle no less frequently than once every 28.56 ms, we are sure that the measured rotational angle between two consecutive readings is accurate. If we measure less frequently, it is possible that our measured angle is less than the wheel's actual rotation by one or more complete rotations of the wheel.

In order to monitor both speed and acceleration, it is necessary to perform trend analysis on readings from the wheel sensor. We have chosen to base our trend analysis on the most recent eight consecutive readings from that sensor, and we have configured our system to capture a wheel angle measurement every 8 ms. It is important for each reading to be timed at exactly the right moment. If the measurement is performed just 1 ms late, the calculated speed will be off by the amount shown in Figure 3:

realtime-pt1-f3

Figure 3. Calculated speed

Thus, we impose a real-time constraint on our system that each wheel sensor reading be performed within 400 microseconds (μs) of its intended sample time. This limits the error in speed calculations to 5 percent.

For trend analysis, we perform the symbolic analysis required to calculate a least-squares best-fit line segment to represent the scatter diagram of instantaneous velocity versus time. Consider the sample data shown in Table 1, which was collected by an actual run of the program described in this article.

Table 1. Simulated wheel sensor data
Time of Measurement (x values) Measured Angle Angular Difference from Previous Measurement Distance Traveled in Feet (y values)
-0.008 s 105.3335    
0 s 137.8311 32.4976 0.5672
0.008 s 170.3579 32.5268 0.5677
0.016 s 202.9141 32.5562 0.5682
0.024 s 235.4997 32.5856 0.5687
0.032 s 268.1146 32.6149 0.5692
0.040 s 300.7588 32.6442 0.5698
0.048 s 333.4324 32.6736 0.5703
0.056 s 6.1353 32.7039 0.5708

For the straight-line equation in the form y = mx + b, the formulas for calculating the slope m and the y-intercept b of the best-fit line are as shown in Figure 4:

realtime-pt1-f4

Figure 4. Formulas for calculating the slope and y-intercept

A graph of this data is shown in Figure 5.

realtime-pt1-f5

Figure 5. Speeds measured by rotational angle sensor

During the time span represented by these measurements, the tire was known to be accelerating at a constant rate of 8 ft/sec/sec. In other words, every second, it was traveling 8 ft/sec faster than in the previous second. Any jitter in measurement of wheel angles is manifested as outliers in the trend analysis. If two consecutive measurements are taken less than 8 ms apart, the reading is manifested below the fitted line. If two consecutive measurements are taken more than 8 ms apart, the reading is manifested above it.

The measurements in Figure 5 were taken on a lightly loaded Microsoft Windows 7 machine running the Java HotSpot Client VM. If the host computer had been more heavily loaded, or if the Java SE application threads had been preempted by garbage collection or by dynamic adaptive just-in-time (JIT) compilation, the measured data points would not be so tightly aligned. We'll learn techniques for managing these issues in Part 2 of this series.

The astute reader might ask why we don't simply measure the time at which each measurement is taken and "plot that" rather than assuming every measurement is taken exactly 8 ms after the previous measurement. That would be a reasonable alternative approach, except for the following observations.

  • First, if you are trying to use time stamps because you've decided it's too difficult to engineer compliance with real-time constraints, realize that you'll also need to solve some difficult real-time engineering problems to ensure that the time stamps themselves are valid. This is because a thread can be preempted between when it captures the time stamp into its private memory and when it performs the action that it is trying to monitor.
  • Second, the calculations required to perform best-fit trend analysis are much simpler if we can assume that the samples are equidistant in time.
  • Third, the underlying assumption of real-time system engineering is that we are taking proactive control over when things occur. Our ability to do so is critical to the certification arguments that are fundamental to ensuring the system's proper operation. If we cannot control when wheel sensor measurements are taken, how can we argue that the antilock braking system will properly disengage, within a predetermined upper time limit, a wheel that is locked in a skid?

Choosing to assume that measurements are taken at a fixed periodic interval is one of the distinguishing characteristics of typical real-time software system designs.

The slope of the best-fit line represents the measured acceleration. The sensor system has correctly approximated that the acceleration is 8 ft/sec/sec, as shown by the calculation in Figure 6:

realtime-pt1-f6

Figure 6. Acceleration calculation

Java SE Implementation of the Traction Monitoring System

The sample application is structured as a number of event handlers. Each event handler is a thread that waits for a periodic release, executes the event handling code, and then suspends itself until the next periodic release. The traction monitoring system comprises the following event handlers:

  • Four different WheelSensor event handlers, one per wheel, with a period of 8 ms
  • A BrakeMonitor event handler, with a period of 16 ms
  • A SteeringMonitor event handler with a period of 24 ms
  • An AcceleratorMonitor event handler with a period of 32 ms

The following additional threads participate as part of the underlying infrastructure:

  • An overseeing main thread, which periodically checks and reports on the status of vehicle traction, with a period of 100 ms
  • A dispatcher thread, which manages the scheduling of the other real-time threads, with a period of 4 ms
  • A PeriodicDelay thread, which provides interconnection between the dispatcher thread and the main thread, with a period of 100 ms

Code for the AcceleratorMonitor event handler is shown in Listing 1.

[1]    package traction;
[2] 
[3]    import com.atego.srt.EventHandler;
[4]    import simulation.IOServer;
[5] 
[6]    public class AcceleratorMonitor extends EventHandler {
[7] 
[8]      private double acceleration;
[9]      private final IOServer io;
[10]      public AcceleratorMonitor(IOServer io, int priority) {
[11]        super(priority);
[12]        this.io = io;
[13]        acceleration = 0.0;
[14]      }
[15]      public synchronized void handleAsyncEvent() {
[16]        acceleration = io.getAcceleration();
[17]      }
[18]      public synchronized double getAcceleratorLevel() {
[19]        return acceleration;
[20]      }
[21]    }

Listing 1. Implementation of AcceleratorMonitor

The AcceleratorMonitor class extends the abstract com.atego.srt.EventHandler class, which is from an open-source library that provides capabilities similar to the services discussed in Real-Time Specification for Java (Addison Wesley Longman, 2000). The concrete implementation overrides the handleAsyncEvent method, providing user code that is to be executed every time the event handler is fired.

In this case, the event handler simply fetches the acceleration reading from the IOServer object passed in to the AcceleratorMonitor's constructor and stores the returned value in a private instance variable. When the application desires to know the current acceleration level, it invokes the getAcceleratorLevel method. Note that both the handleAsyncEvent and getAcceleratorLevel methods are synchronized. This is because these two methods, which are normally executed by different threads, share access to the same acceleration variable. Synchronization is required both to ensure coherent access to the two-word data value and to enforce visibility across thread boundaries.

In the provided source code, the IOServer object is an abstract representation of the test vehicle. It provides simulation data as guided by a real-time script. It would be straightforward to replace the IOServer argument with a subclass that provides access to a real vehicle's sensor system or with an interface to gaming controls for a brake pedal, accelerator, and steering wheel.

The AcceleratorMonitor object is instantiated within Main.java and assigned to the local accelerator_monitor variable. To arrange for periodic execution of the AcceleratorMonitor, it is passed as an argument to a PeriodicEvent constructor, as shown in the following excerpt from Main.java:

PeriodicEvent accelerator_event = 
   new PeriodicEvent(dispatcher, ACCELERATOR_SENSOR_PERIOD, accelerator_monitor);

The dispatcher object takes responsibility for firing the PeriodicEvent with the specified period.

The event handling code for the BrakeMonitor and SteeringMonitor objects is essentially the same as for the AcceleratorMonitor and is not discussed in the article.

The WheelSensor event handler is more interesting because it takes responsibility for the trend analysis associated with the estimation of instantaneous speed and acceleration.

The code for the event handler is shown in Listing 2. This event handler is executed every 8 ms. With each invocation, it fetches the current wheel angle and updates the running tallies on the variables required to update the best-fit analysis of speed trends.

Note that, where possible, we avoid iterating over all elements of the scatter chart. In particular, in lines 26 through 28, we update the sum_y and average_y values by subtracting the overwritten value and adding in the newly computed distance value. Note also that we don't recompute the values of the x_values array each time the event handler executes. Those values are set once in the constructor, which is not shown, and never change.

[1]    long num_samples, least_squares_tag;
[2] 
[3]    public synchronized void handleAsyncEvent() {
[4]        double rotation = io.getWheelRotation(wheel_id);
[5]        double adjusted_rotation = (rotation < previous_angle) ? (
[6]          rotation + 360: rotation);
[7]        double rotational_delta = adjusted_rotation - previous_angle;
[8]        double this_distance = (
[9]          (rotational_delta / 360.0) * WHEEL_CIRCUMFERENCE);
[10]        previous_angle = rotation;
[11]        double overwritten_distance = y_values[oldest_distance_index];
[12]        y_values[oldest_distance_index] = this_distance;
[13]        oldest_distance_index++;
[14]        if (oldest_distance_index >= NUM_SAMPLES) {
[15]          oldest_distance_index = 0;
[16]        }
[17]        sum_xy = 0.0;
[18]        int y_index = oldest_distance_index;
[19]        for (int i = 0; i < NUM_SAMPLES; i++) {
[20]          sum_xy += x_values[i] * y_values[y_index];
[21]          y_index++;
[22]          if (y_index >= NUM_SAMPLES) {
[23]            y_index = 0;
[24]          }
[25]        }
[26]        sum_y -= overwritten_distance;
[27]        sum_y += this_distance;
[28]        average_y = sum_y / NUM_SAMPLES;
[29]        num_samples++;
[30]      }

Listing 2: Implementation of WheelSensor.handleAsyncEvent()

When application code desires to know a current wheel speed or acceleration, it invokes the getSpeed or getAcceleration method, which are shown in Listing 3. As with the implementation of AcceleratorMonitor, both of these methods and handleAsyncEvent are all synchronized. The private calculateLeastSquaresLine method relies on the synchronization provided by the methods that invoke it.

The WheelSensor object uses the instance variable num_samples to count the number of times the handleAsyncEvent method has been invoked. The calculateLeastSquaresLine uses the least_squares_tag instance variable to remember which sample data the most recent calculation was based on. The test at line 4 allows the method to avoid recomputing the slope and intercept if calculateLeastSquaresLine is invoked again before new data has been fetched.

[1]      private double m, b;
[2] 
[3]      private void calculateLeastSquaresLine() {
[4]        if (num_samples != least_squares_tag) {
[5]          m = (sum_xy - sum_x * sum_y / NUM_SAMPLES) / m_denominator;
[6]          b = average_y - m * average_x;
[7]          least_squares_tag = num_samples;
[8]        }
[9]      }
[10]      
[11]      // Current speed is returned in ft/sec
[12]      public synchronized double getSpeed() {
[13]        calculateLeastSquaresLine();
[14]        double result = (b + m * polling_span) / polling_period;
[15]        return result;
[16]      }
[17]    
[18]      // Acceleration is the change in speed. Units of result are ft/sec/sec.
[19]      public synchronized double getAcceleration() {
[20]        calculateLeastSquaresLine();
[21]        // The slope is ft/8 ms per second. Divide by
[22]        // polling_period to convert to ft/sec/sec.
[23]        return m / polling_period;
[24]      }

Listing 3. Implementation of key WheelSensor methods

Architectural Considerations

For an application as simple as the traction monitoring system, it might seem unnecessarily complex to structure this as ten independent threads (main, dispatcher, periodic delay, four wheel sensors, brake sensor, acceleration sensor, and steering sensor). A simpler program structure might have been a single thread running in a tight loop, with a sleep request at the top of the loop to delay an appropriate amount of time between successive sensor readings. In fact, early real-time systems were almost always implemented using that alternative approach, which is characterized as static cyclic scheduling.

Most modern real-time systems are structured in the style of the sample traction monitoring application. The reasons for this more-dynamic task model are several fold:

  • While many of the tasks in a typical real-time system are periodic in nature, others are asynchronous, meaning it is not possible to plan prior to execution when the corresponding event handlers need to be "fired." Examples of asynchronous events include opening of a door or window, depression of an emergency stop button, or loss of pressure in an air-brake hose. Inserting idle times into the static cyclic schedule to accommodate the urgent processing needs of potential firings of asynchronous event handlers is wasteful of available processing resources.
  • The size and complexity of typical embedded real-time systems has grown exponentially in recent years. Whereas twenty years ago, it was generally expected that the entirety of a real-time software system would be implemented by one or two developers working in close concert, with each developer having an intimate understanding of the complete system, today's real-time software systems often consist of millions of lines of code with hundreds of independent tasks. Building static cyclic schedules for such large and complex problem sizes is often considered impractical.
  • Many modern real-time systems have tens or hundreds of logical tasks with different periodic behaviors and different response-time constraints. Optimal scheduling for compliance with real-time constraints might require preemption of lower-priority tasks by higher-priority tasks. To use static cyclic scheduling, longer-running low-priority tasks need to be divided into multiple bounded-time subtasks within the static schedule. Dividing a logical task into multiple independent time-bounded subtasks is difficult, and maintaining this division adds to the costs and complexity of normal software maintenance and evolution activities.
  • A trend among microprocessor vendors is to structure computers with multiple cores. The best way to exploit the processing capacity of these modern microprocessors is to divide the necessary work into multiple independent tasks.

While there still exist today many real-time systems that use static cyclic schedules, these are mostly the smaller and simpler systems. Since the real-time systems that are most likely to be implemented in Java SE are the ones that are larger, more complex, and more dynamic, the emphasis of this article is on priority-based scheduling.

Compliance with Real-Time Constraints

As demonstrated by the quality of the fit to the trend line reported in Figure 5, this application is doing a very good job of meeting its real-time constraints. In a 30-second simulation, the deviance from intended real-time constraints was never so great as to result in a false indication of wheel slippage. In part, this is because we were very careful to engineer this application so that there would be no cause for inconsistent timing behavior (also known as jitter).

A careful reading of the complete application source code proves that the only memory allocation performed by this application is during startup, before we actually begin running the periodic real-time tasks. Furthermore, we have carefully avoided any dependencies on I/O services that might block execution while, for example, when a disk arm is seeking a new track. And we have been very careful to ensure that this is the only software running on the host platform while we are taking our real-time measurements.

Conclusion

This traction monitoring application has been implemented in a style that facilitates engineered compliance with real-time constraints. However, we have not yet explained how that engineering is performed. That is the topic of the next article in this series.

Particular topics that will be described, using this traction monitoring application to motivate the discussion, include the theory of rate-monotonic scheduling, priority inheritance, workload admission analysis, real-time garbage collection, and special virtual machine implementation approaches, all of which are designed to enable efficient deployment of real-time software written in Java SE.

Careful application of these principles of real-time software engineering allow us to build systems that are much more complex than the small traction monitoring application yet still comply reliably with real-time constraints.

See Also

About the Author

As Chief Technology Officer over Java at Atego Systems—a mission- and safety-critical solutions provider—Dr. Kelvin Nilsen oversees the design and implementation of the Perc Ultra virtual machine and other Atego embedded and real-time oriented products. Prior to joining Atego, Dr. Nilsen served on the faculty of Iowa State University where he performed seminal research on real-time Java that led to the Perc family of virtual machine products. Dr. Nilsen participates in the Java Community Process as a member of the JSR 282 and JSR 302 Expert Groups. He holds a BS in physics and MS and PhD degrees in computer science. Atego Systems is a wholly owned subsidiary of PTC, Inc.

Join the Conversation

Join the Java community conversation on Facebook, Twitter, and the Oracle Java Blog!