The aims of this exercise are:
The exercise is developed for Linux (Fedora, Java8). However, the exercises can also be done on other platforms, e.g. Windows or Solaris.
The course home page is http://www.control.lth.se/education/engineering-program/frtn01-real-time-systems/. Make sure that you are familiar with the contents of this page. The most important parts are the News and the Course Material.
When programming in Java it is useful to always have a web-browser tab open that shows the JavaDoc documentation for Java.
It is also useful to have a browser tab open that shows the API for the local classes in se.lth.control.*
Oracle's Java tutorial is a good source of information about Java.
Both gedit and geany are easy to learn plain text editors with ordinary hotkeys. If the code is not auto-indented when you press enter you need to enable it in the settings. Programs written in these editors need to be compiled and executed from a terminal.
Eclipse is a powerful software development environment. However, learning and getting used to Eclipse takes some time and Eclipse may also cause confusion for beginners. Also, the teaching assistants might not be able to help you with problems related to Eclipse.
For those used to Xemacs. Also supports Java debugging with the JDE Java-mode. The teaching assistants might not be able to help you with problems related to Xemacs or JDE.
This exercise involves writing a simple Java program, Periodic, that uses threads. It is essential that you understand the meaning of all words that are indicated by italic letters.
The program will start a number of threads that execute periodically. The threads simply print their period on standard output once each period as below:
> java Periodic 500 2000 500, 2000, 500, 500, 500, 500, 2000, 500, 500, 500, 500, 2000, ...
The program starts one thread per input argument, with the period in milliseconds specified by that argument.
public class Periodic extends Thread { }
The class is declared to extend the Thread class, so that instances of the class may run as threads. Another way to express this is that Periodic is a subclass of Thread. In this program we want to be able to specify a period for each thread, i.e. each instance of Periodic. Therefore we add an instance variable to hold the period for each Periodic instance:
private int period;
> javac Periodic.java
If the compilation returns with errors or warnings you should check your code. If running outside Lab A, also make sure that your Java environment is setup correctly.
public static void main(String[] args) { }
The main() method is called once by the JVM when running a class as a program.
> java Periodic
Of course not much happens yet. Actually no thread was created when we ran the program, because no instance of the class was created.
To create a class instance, or an object, we need to call a constructor method of the class. When we create a Periodic instance we also want to set the period of that instance. Therefore a constructor method is defined as
public Periodic(int period) { this.period = period; }
Now let main() call the constructor method:
Periodic p = new Periodic(1000);
A Periodic object (which is also a Thread object) with period 1000 ms was created when we ran the program, and a reference to that object was stored in the variable p. But still nothing visible happened, because we have not specified what code the thread should execute, neither have we started the thread.
The code that a running Thread object executes is defined in the run() method. The run() method of the Thread class is defined as empty. To include our own code we override the run() method in our subclass Periodic. We want the object to print its period on standard output. Therefore we define it as
public void run() { System.out.print(period); System.out.print(", "); }
To start the thread we call the start() method of the Thread class after the object creation in main(), i.e.
p.start();
Now we are getting somewhere! The Periodic thread printed its period on the standard out stream, but only once. This is because the run() method terminated after printing once, and as a result the thread stopped. To make the thread print periodically we need to add a loop and make it wait period milliseconds before printing the next. Let us use a while loop:
while (!Thread.interrupted()) { ... } System.out.println("Thread stopped.");
This makes the thread run until the thread is interrupted (requested to stop). To make the thread wait for a certain time we call the static sleep() method of the Thread class.
try { while (!Thread.interrupted()) { ... Thread.sleep(period); } } catch (InterruptedException e) { // Requested to stop } System.out.println("Thread stopped.");
Since Thread.sleep() may throw an InterruptedException if the sleep is interrupted, we need to use the try/catch construction. Here it encloses the while loop to make sure that the thread is stopped if an InterruptedException is caught (if an InterruptedException is thrown the Thread's interrupted status is cleared). If we would have put the try/catch around the Thread.sleep() only, then the loop would not exit, and interrupting the thread would not cause it to exit.
The next step is to create multiple threads with different periods to run in parallel.
public class Periodic extends Thread { private int period; public Periodic(int period) { this.period = period; } public void run() { try { while (!Thread.interrupted()) { System.out.print(period); System.out.print(", "); Thread.sleep(period); } } catch (InterruptedException e) { // Requested to stop } System.out.println("Thread stopped."); } public static void main(String[] args) { Periodic p = new Periodic(1000); p.start(); new Periodic(2000).start(); } }
The last step is to read the input arguments to the program and start the corresponding threads. The program input arguments are delivered to the main() method in the args argument, as an array of String objects. The method Integer.parseInt() converts a String to an int. The for construction can be used to loop through an array, e.g. for (String arg : args).
> java Periodic 500 1000
See if you can find situations when the output on the screen is corrupted. The screen is a shared resource. In Exercise 2 you will learn how to ensure that shared resoures are accessed under mutual exclusion.
public class Periodic extends Thread { private int period; public Periodic(int period) { this.period = period; } public void run() { try { while (!Thread.interrupted()) { System.out.print(period); System.out.print(", "); Thread.sleep(period); } } catch (InterruptedException e) { // Requested to stop } System.out.println("Thread stopped."); } public static void main(String[] args) { for (String arg : args) new Periodic(Integer.parseInt(arg)).start(); System.out.print("(Number of active threads = " + Thread.activeCount() + "), "); } }The number of threads involved in the execution of the command is three. The program starts two Periodic threads, and there is one Main thread executing the main method. The main thread is started by the JVM.
A drawback with creating threads by extending the Thread class is that it is not possible for the class to be a subclass of some class other than Thread, e.g. of some user-defined class. The reason for this is that Java only supports single inheritance, and not multiple inheritance. An alternative way is to specify that the class implements the Runnable interface:
public interface Runnable { public void run(); }
Since instances of PeriodicRunnable are no longer instances of Thread, starting them is slightly different:
PeriodicRunnable m = new PeriodicRunnable(...); Thread t = new Thread(m); t.start();
public class PeriodicRunnable extends Base implements Runnable { private int period; public PeriodicRunnable(int period) { this.period = period; } public void run() { try { while (!Thread.interrupted()) { System.out.print(period); System.out.print(", "); Thread.sleep(period); } } catch (InterruptedException e) { // Requested to stop } System.out.println("Thread stopped."); } public static void main(String[] args) { for (String arg : args) { PeriodicRunnable p = new PeriodicRunnable(Integer.parseInt(arg)); Thread t = new Thread(p); t.start(); } } }
A third possibility is to have threads as inner classes, i.e. classes that are defined within another class. The inner thread classes typically extend Thread.
Hint: You cannot create an instance of an inner class without first creating an instance of the outer class.
public class PeriodicWithInnerThread extends Base { private int period; private PeriodicThread t; public PeriodicWithInnerThread(int period) { this.period = period; t = new PeriodicThread(); } public void start() { t.start(); } private class PeriodicThread extends Thread { public void run() { try { while (!Thread.interrupted()) { System.out.print(period); System.out.print(", "); Thread.sleep(period); } } catch (InterruptedException e) { // Requested to stop } System.out.println("Thread stopped."); } } public static void main(String[] args) { for (String arg : args) { PeriodicWithInnerThread p = new PeriodicWithInnerThread(Integer.parseInt(arg)); p.start(); } } }
An advantage with inner classes is that all variables and methods visible in the outer class can be directly accessed from the inner class. Another advantage is that it is possible for an object to contain multiple execution threads.
It is also possible to let the inner class be an anonymous class, i.e., a class with no name. Using this approach the new class PeriodicWithAnonymousThread would contain the following code:
public class PeriodicWithAnonymousThread extends Base { ... Thread t = new Thread() { public void run() { // Code to be executed } }; ... }
public class PeriodicWithAnonymousThread extends Base { private int period; private Thread t; public PeriodicWithAnonymousThread(int per) { period = per; t = new Thread() { public void run() { try { while (!Thread.interrupted()) { System.out.print(period); System.out.print(", "); Thread.sleep(period); } } catch (InterruptedException e) { // Requested to stop } System.out.println("Thread stopped."); } }; } public void start() { t.start(); } public static void main(String[] args) { for (String arg : args) { PeriodicWithAnonymousThread p = new PeriodicWithAnonymousThread(Integer.parseInt(arg)); p.start(); } } }
Drawback with anonymous classes are that they can only be instantiated where defined and the code might become less readable. Anonymous classes are often used to implement event listeners in Swing.
All threads have a priority. In Java, a higher number means higher priority. If no priority is set the thread gets a default priority.
In real-time applications the priorities of the threads are very important. Investigate how you change the priorities of a thread.
public class Periodic extends Thread { private int period; public Periodic(int period) { this.period = period; } public void run() { System.out.println("Priority = " + getPriority()); setPriority(getPriority() + 1); try { while (!Thread.interrupted()) { System.out.print(period); System.out.print(", "); Thread.sleep(period); } } catch (InterruptedException e) { // Requested to stop } System.out.println("Thread stopped."); } public static void main(String[] args) { for (String arg : args) new Periodic(Integer.parseInt(arg)).start(); } }PeriodicRunnable.java:
public class PeriodicRunnable extends Base implements Runnable { private int period; public PeriodicRunnable(int period) { this.period = period; } public void run() { Thread t = Thread.currentThread(); System.out.println("Priority = " + t.getPriority()); t.setPriority(t.getPriority() + 1); try { while (!Thread.interrupted()) { System.out.print(period); System.out.print(", "); Thread.sleep(period); } } catch (InterruptedException e) { // Requested to stop } System.out.println("Thread stopped."); } public static void main(String[] args) { for (String arg : args) { PeriodicRunnable p = new PeriodicRunnable(Integer.parseInt(arg)); Thread t = new Thread(p); t.start(); } } }
Java has two different thread models. In the green-thread model the virtual machine (VM) is responsible for the scheduling of the Java threads, which only exist as abstractions inside the VM. In the native-thread model the Java threads are mapped upon the threads of the underlying operating system, i.e. the Linux threads. The scheduling of the Java threads is then subject to the underlying scheduling of the threads by the operating system.
The green-thread model is the standard model for reference implementations of Java. Since it does not require any thread support in the operating system, it is very portable. However, for efficiency reasons modern VMs such as the Linux HotSpot VM used in the course use the native-thread model.
In the course we use standard Java and pretend that it is a real-time language, although in reality it is not. However, we want our programs to behave in a way that functionally is the same as if the programs would have been written in some real-time language executing on some real-time operating system. For example, we do not want a low priority thread to preempt a high priority thread.
Consider the following example:
public class StarvationTest { public static void main(String[] args) { Thread t_low = new LowPrioThread(); t_low.start(); Thread t_high = new HighPrioThread(); t_high.start(); } private static class LowPrioThread extends Thread { public void run() { setPriority(7); System.out.println("Low priority thread started."); try { while (!Thread.interrupted()) { System.out.println("Low priority thread executing"); Thread.sleep(1000); } } catch (InterruptedException e) { // Requested to stop } System.out.println("Thread stopped."); } } private static class HighPrioThread extends Thread { private double sum; public void run() { setPriority(8); System.out.println("High priority thread started."); while (!Thread.interrupted()) { sum = sum + Math.random(); } System.out.println("Thread stopped."); } } }
This is an example where the programmer has made a design error. The high priority thread never releases the CPU. The result will be that the low priority thread is starved, it never gets the chance to execute. The solution is either to let the high-priority thread release the CPU, e.g. by doing a sleep() in the loop, or to give it lower priority than the low priority thread.
> javac StarvationTest.java > java StarvationTest High priority thread started. Low priority thread started. Low priority thread executing Low priority thread executing Low priority thread executing ...
The problem is that the two Linux threads used in the native-thread model to execute the two Java threads have the same priority and use round-robin time-slicing. The first Linux thread executes the high priority thread. After a while its time slice expires, and the next Linux thread starts executing. It chooses the next available Java thread, the low-priority thread, and starts executing it.
This behaviour is NOT the behaviour that we want on a uni-processor machine. It makes it difficult to detect errors caused by poorly written code. The user believes that everything is correct, even when this is not the case.
Note: This part of the exercise can only be performed on the Linux computers in Lab A as it requires a modified version of Java.
In order to achieve the desired behavior, we need to access native Linux functions to set priorities and other scheduling parameters. The computers in the lab have been fitted with a thread class extended with native function calls as shown below.
public class LinuxRTThread extends Thread { int tid; /* Native thread id */ int prio; /* Native priority */ /* The exercise is set up so that the JVM starts with priority 20. Do not create threads with priority greater than 19. */ public static int DEFAULT_RT_PRIO = 1; private native int setRTPrio(int tid, int prio); private native int getMyTid(); /* Must be called first in the run() method. * Thread.setPriority() is declared final * and cannot be overriden. */ public synchronized void setPrio(int prio); /* Returns the native thread id */ public synchronized int getTid(); /* Returns the native thread priority */ public synchronized int getPrio(); }
We are unable to override Thread.setPriority() as it is declared final. Our modified method is therefore named setPrio().
public class StarvationTestRT { public static void main(String[] args) { Thread t_low = new LowPrioThread(); t_low.start(); int numHighPriorityThreadsToStart = 1; for (int l = 0; l < numHighPriorityThreadsToStart; l++) { Thread t_high1 = new HighPrioThread(); t_high1.start(); } } private static class LowPrioThread extends LinuxRTThread { public void run() { setPrio(7); System.out.println("Low priority thread started."); try { while(!Thread.interrupted()) { System.out.println("Low priority thread executing"); Thread.sleep(1000); } } catch (InterruptedException e) { // Requested to stop } System.out.println("Thread stopped."); } } private static class HighPrioThread extends LinuxRTThread { private double sum; public void run() { setPrio(8); System.out.println("High priority thread started."); while (!Thread.interrupted()) { sum = sum + Math.random(); } System.out.println("Thread stopped."); } } }
How does this program behave? Is the result what you expect? If not, try creating and starting some additional high priority threads. Is the behaviour different? Why?
> rtjavac StarvationTestRT.java > rtjava StartvationTestRT Low priority thread started. High priority thread started. Low priority thread executing High priority thread started. High priority thread started. High priority thread started.before the program stops writing anything to the terminal.
The reason that you do not experience any starvation of the low priority thread until you have created several high priority threads is the fact that the machines that you are using in Lab A have more than one processor (they are quad-core machines). This means that even if the two Linux threads behave like real-time threads, still both of them would execute, but on different cores.