Multithreading, the Thread class, and the Runnable interface

 

Overview

In every day life, you frequently perform two or more tasks simultaneously. For example, you could be talking on the telephone while having a cup of coffee and watching television. Although you might think you are performing all three tasks at the same time, you really aren't. By quickly shifting your attention from one activity to another, you are able to complete all three tasks without actually performing any two at precisely the same time. In addition, the tasks may have different priorities. One may be given more attention (time) than another and these priorities may change.

In a similar manner, a Java program can perform two or more processes simultaneously. When it does, it is called multithreading.

 

Multithreading

 

The life of a thread

In Java, all threads are objects that are constructed, pass between several "states", and die in keeping with the following diagram:

Runnable

Blocked

New

--->

 

 

Ready  <--->  Running

 

 

<--->

 

sleeping

waiting

suspended

 

\

Dead
  1. When initially constructed, the thread is New

  2. When all resources the thread requires are available, it enters the Ready state. The thread is ready to use the CPU.

  3. When selected by the JVM for processing, the thread enters the Running state. This is the state that all threads aspire to but only one is ever running at an instant in time.

  4. When the thread requests to sleep for an interval of time, must wait for a resource (such as completion of I/O), or is suspended by the JVM, it becomes Blocked. It gives up the CPU and will return to the Ready state when the block is removed.

  5. When the thread completes its processing or is killed by the JVM, it enters the Dead state. The thread object will still exist, but will no longer be allowed to use the CPU.

 

The Thread class

Object 

\

Thread 

public class GetMad extends Thread {
  public void run() {
    for (int i = 1; i <= 10; i++) {
      System.out.println(i);
    }
    System.out.println("DONE");
  }
}

defines a class to count from 1 to 10 as a thread. For an application to create and launch the thread, all that must be coded are the statements:

GetMad t = new GetMad();
t.start();

The inherited start() method of the Thread class automatically calls the run() method to trigger the thread's processing. A program should never call the run() method directly.

Once the thread has been launched, the application can continue its own processing thread with the JVM scheduling the use of the CPU by the two threads. Here is a complete example using a static inner class:

public class App {

  public static class GetMad extends Thread {
    public void run() {
      for (int i = 1; i <= 10; i++) {
        System.out.println(i);
      }
    }
  }

  public static void main(String[] args) {

    GetMad t = new GetMad();
    t.start();
    for (int i = 1; i <= 100; i++) {
      System.out.println("Working");
    }
  }
}

When this is run, note how the output of the two threads is interleaved.

Thread aThread = new Thread(threadObject);

where threadObject is the reference of an object that either extends the Thread class or implements the Runnable interface.

Using this technique, an alternative way to have constructed the thread of the previous sample would be

Thread t = new Thread(new GetMad());

Method

Usage

destroy()

A method that may be overridden to define what is to be done when the thread is to be destroyed

getName()

Returns a String representing the thread's name

getPriority()

Returns an int from 1 to 10 that indicates the thread's priority. The highest priority is 10.

interrupt()

Interrupts the thread

isAlive()

Returns a boolean indicating if the thread is currently alive

isInterrupted()

Returns a boolean indicating if the thread is currently interrupted

run()

A method that may be overridden to define thread processing. It should never be called directly.

setName()

Sets the name of the thread to the specified String

setPriority()

Sets the priority of the thread to the specified int value (from 1 to 10)

sleep()

Causes the currently executing thread to sleep for a specified number of milliseconds. This is a static (class) method.

start()

Causes this thread to begin execution by automatically calling its run() method

yield()

Causes the currently executing thread to give up the CPU so that other threads may execute. This is a static (class) method.

Note that the stop(), suspend(), and resume() methods of the Thread class are deprecated and should be avoided. They are unsafe and may result in errors.

Consult the Java API documentation for more details.

public class App {
  public static void main(String[] args) {
    System.out.println("Going to sleep for 3 seconds...");
    try {
      Thread.sleep(3000);
    }
    catch (InterruptedException err) {
    }
    System.out.println("Now I'm awake!");
  }
}

Note that sleep() is a static method (requires no object) and may throw an InterruptedException. A method that calls Thread.sleep() must either use try and catch blocks (as shown in this example) or claim the possibility of throwing the exception.

Thread.yield();

Note that yield() is a static method (requires no object) and throws no exceptions. It is a good practice for long-running threads to periodically yield and allow other threads the opportunity to use the CPU.

public class App {

  // This inner class encapsulates the processing of an Alarm thread
  // that will display a message at a specified time interval.

  public static class Alarm extends Thread {

    // Instance variables.

    private int delayInSeconds;
    private int count;
    private boolean isAlive;

    // This method constructs an Alarm thread. It receives the time
    // interval in seconds, sets the count to zero, and indicates that
    // the thread is alive.

    public Alarm(int seconds) {
      delayInSeconds = seconds;
      count = 0;
      isAlive = true;
    }

    // This method defines thread processing. The thread will loop
    // while alive to display a message at the time interval.

    public void run() {
      while (isAlive) {
        try {
          Thread.sleep(delayInSeconds * 1000);
        }
        catch (InterruptedException err) {
        }
        count += delayInSeconds;
        System.out.println(getName() + ": " + count);
      }
    }

    // This method can be called to destroy the thread.

    public void destroy() {
      isAlive = false;
    }
  }

  // This is the starting point for application processing.

  public static void main(String[] args) {

    // Instantiate three Alarm threads with intervals of 1, 5, and 10
    // seconds and start their processing.

    Alarm t1 = new Alarm(1);
    Alarm t2 = new Alarm(5);
    Alarm t3 = new Alarm(10);
    t1.start();
    t2.start();
    t3.start();

    // Sleep for 20 seconds.

    try {
      Thread.sleep(20000);
    }
    catch (InterruptedException err) {
    }

    // Destroy all threads and release their resources.

    t1.destroy();
    t1 = null;
    t2.destroy();
    t2 = null;
    t3.destroy();
    t3 = null;
  }
}

Notes:

  1. The value of the boolean variable isAlive is used to control thread processing in the run() method.

  2. The destroy() method overrides the default destroy() method inherited from the Thread class to set the value of isAlive to false (and stop thread processing).

 

The Runnable interface

public void run()

The following is a sample applet that contains an inner class for a blinking button. By extending the Button class, BlinkButton inherits normal button features. By implementing the Runnable interface and defining a run() method, it adds thread processing to make the button blink at a specified interval set by its constructor.

import java.awt.*;
import java.awt.event.*;
import java.applet.*;

public class App extends Applet implements ActionListener {

  // This inner class defines a blinking button. The Runnable
  // interface is implemented and the run() method defined in
  // order for an object to be run as a thread.

  public class BlinkButton extends Button implements Runnable {

    // For holding the blink interval in milliseconds.

    int interval;

    // This method constructs a blinking button object. It receives
    // the button's label (which is passed through to the superclass
    // constructor) and the blink speed in milliseconds (which is
    // saved). It then creates and starts a thread for this object.

    public BlinkButton(String label, int blinkSpeed) {
      super(label);
      interval = blinkSpeed;
      new Thread(this).start();
    }

    // This method is required by the Runnable interface and defines
    // thread processing.

    public void run() {

      // Endless loop to sleep for the specified interval then awake
      // to swap the button's foreground and background colors.

      while (true) {
        try {
          Thread.sleep(interval);
        }
        catch(InterruptedException err) {
        }
        Color oldForeground = getForeground();
        setForeground(getBackground());
        setBackground(oldForeground);
      }
    }
  }

  // Object references for this applet.

  BlinkButton b;
  TextField message;

  // This method begins applet processing.

  public void init() {

    // Resize the applet and set its background color.

    resize(300, 100);
    setBackground(Color.lightGray);

    // Create the blink button and add it to the applet.

    b = new BlinkButton("Show message", 500);
    b.setBackground(Color.black);
    b.setForeground(Color.white);
    b.addActionListener(this);
    add(b);

    // Create the message text field and add it to the applet.

    message = new TextField("", 15);
    message.setFont(new Font("Serif", Font.ITALIC, 24));
    message.setForeground(Color.red);
    message.setEditable(false);
    add(message);
  }

  // This method is automatically called to handle the user
  // clicking the blink button.

  public void actionPerformed(ActionEvent e) {

    // If the message is not currently displayed, display it.
    // Otherwise, clear the message.

    if (message.getText().length() == 0) {
      message.setText("Hello world!");
      b.setLabel("Clear message");
    }
    else {
      message.setText("");
      b.setLabel("Show message");
    }
  }
}

 

Looking ahead

Multithreading is a powerful tool, but can be dangerous when two or more threads share the same resource. If not properly managed, one thread can easily overlay or undo the work of another thread. This will be addressed in the next lesson.

 

Lab exercise for Ferris students

E-mail your answers to this assignment no later than the due date listed in the class schedule.

 

Review questions

  1. What will be displayed when an attempt is made to compile and execute the following program? The line numbers are for reference purposes only.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class App {
  public static class Inner extends Thread {
    public void run(int x) {
      for (int i = 0; i < 3; i++) {
        System.out.println("Thread Running");
      }
    }
  }
  public static void main(String[] args) {
    Inner x = new Inner();
    Thread t = new Thread(x);
    t.start();
  }
}
  1. a compile error will occur at line 2

  2. a compile error will occur at line 3

  3. a compile error will occur at line 12

  4. the program will compile and execute to display the message "Thread Running" 3 times 

  5. the program will compile and execute but nothing will be displayed

  1. What will be displayed when an attempt is made to compile and execute the following program? The line numbers are for reference purposes only.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class App {
  public static class Inner extends Thread {
    String myName;
    public Inner(String name) {
      myName = name;
    }
    public void run() {
      while (true) {
        System.out.println(myName);
      }
    }
  }
  public static void main(String[] args) {
    Thread t1 = new Thread(new Inner("Bob"));
    t1.setPriority(4);
    t1.start();
    Thread t2 = new Thread(new Inner("Sally"));
    t2.start();
  }
}
  1. a compile error will occur at line 14

  2. a compile error will occur at line 15

  3. the program will compile and execute to display "Bob" more than "Sally

  4. the program will compile and execute to display "Sally" more than "Bob"

  5. the program will compile and execute but nothing will be displayed

  1. What will be displayed when an attempt is made to compile and execute the following program? The line numbers are for reference purposes only.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class App {
  public static class Inner extends Thread {
    public void run() {
      while (true) {
        System.out.println("Sub Thread");
        Thread.yield();
      }
    }
  }
  public static void main(String[] args) {
    new Thread(new Inner()).start();
    while(true) {
      System.out.println("Main Thread");
    }
  }
}
  1. a compile error will occur at line 6

  2. a compile error will occur at line 11

  3. the program will compile and execute to display "SubThread" more than "MainThread

  4. the program will compile and execute to display "MainThread" more than "SubThread"

  5. the program will compile and execute but nothing will be displayed

  1. What will be displayed when an attempt is made to compile and execute the following program? The line numbers are for reference purposes only.

1
2
3
4
5
6
7
8
public class App {
  public static void main(String[] args) {
    while(true) {
      Thread.sleep(10);
      System.out.println("Tick");
    }
  }
}
  1. the program will not compile

  2. the program will compile but a runtime exception will occur

  3. the program will compile and execute to display "Tick" every ten seconds

  4. the program will compile and execute to display "Tick" every ten milliseconds

  5. the program will compile and execute but nothing will be displayed