1.124 Lecture 14 11/3/998

Multithreading

Contents

1. Threads, Processes and Multitasking

Multitasking is the ability of a computer's operating system to run several programs (or processes) concurrently on a single CPU.  This is done by switching from one program to another fast enough to create the appearance that all programs are executing simultaneously.  There are two types of multitasking: Multithreading extends the concept of multitasking by allowing individual programs to perform several tasks concurrently.  Each task is referred to as a thread and it represents a separate flow of control.  Multithreading can be very useful in practical applications.  For example, if a web page is taking too long to load in a web browser, the user should be able interrupt the loading of the page by clicking on the stop button.  The user interface can be kept responsive to the user by using a separate thread for the network activity needed to load the page.

What then is the difference then between a process and a thread?  The answer is that each process has its own set of variables, whereas threads share the same data and system resources.  A multithreaded program must therefore be very careful about the way that threads access and modify data, or else unpredictable behavior may occur.
 

2. How to Create Threads

(Ref. Java Tutorial: http://java.sun.com/docs/books/tutorial/essential/threads/customizing.html, Core Java Vol II Ch 2 page 80, 121)

We can create a new thread using the Thread class provided in the java.lang package.  There are two ways to use the Thread class.

Subclassing the Thread class

In this approach, we create a subclass of the Thread class.  The Thread class has a method named run(), which we can override in our subclass.  Our implementation of the run() method must contain all code that is to be executed within the thread.

class MyClass extends Thread {
    // ...

    public void run() {
        // All code to be executed within the thread goes here.
    }
}
 

We can create a new thread by instantiating our class, and we run it by calling the start() method that we inherited from class Thread.

MyClass a = new MyClass();
a.start();

This approach for creating a thread works fine from a technical standpoint.  Conceptually, however, it does not make that much sense to say that MyClass "is a" Thread.  All that we are really interested in doing is to provide a run() method that the Thread class can execute.  The next approach is geared to do exactly this.

Implementing the Runnable interface

In this approach, we write a class that implements the Runnable interface.  The Runnable interface requires us to implement a single method named run(), within which we place all code that is to be executed within the thread.

class MyClass implements Runnable {
    // ...

    public void run() {
        // All code to be executed within the thread goes here.
    }
}
 

We can create a new thread by creating a Thread object from an object of type MyClass.  We run the thread by calling the Thread object's start() method.

MyClass a = new MyClass;
Thread t = new Thread(a);
t.start();
 

3. The LifeCycle of a Thread

(Ref. Java Tutorial: http://java.sun.com/docs/books/tutorial/essential/threads/lifecycle.html, Core Java Vol II Ch 2 page 85)

A thread can be in one of four states during its lifetime:

The following example illustrates various thread states.  The main thread in our program creates a separate thread, Thread-2.  It then starts Thread-2, thereby making Thread-2 runnable and able to print out an integer every 500 milliseconds.  In the meantime, the main thread goes to sleep for 5 seconds.  After the main thread wakes up, it suspends Thread-2, thereby causing Thread-2 to become blocked.  The main thread then goes to sleep for another 5 seconds.  After the main thread wakes up, it resumes Thread-2, thereby causing Thread-2 to become runnable again.  The main thread then goes to sleep yet again.  When the main thread wake up this time, it stops Thread-2, thereby causing Thread-2 to die.

class MyClass implements Runnable {
    int i;

    public MyClass() {
        i = 0;
    }

    // The run() method simply prints out a sequence of integers, one every half second.
    public void run() {
        while (true) {
            System.out.println(Thread.currentThread().getName() + ": " + i);
            i++;

            try {
                Thread.sleep(500); // Tell the thread to sleep for half a second.
            }
            catch (InterruptedException e) {}
        }
    }
}
 

class Threadtest {
    public static void main(String[] args) {
        MyClass a = new MyClass();
        Thread t = new Thread(a);

        // Start the thread.
        System.out.println(Thread.currentThread().getName() + ": Starting a separate thread");
        t.start();

        // Tell the main program to sleep for 5 seconds.
        try {
            Thread.sleep(5000);
        }
        catch (InterruptedException e) {}

        // Suspend execution of the thread.
        System.out.println(Thread.currentThread().getName() + ": Suspending the thread");
        t.suspend();

        // Tell the main program to sleep for 5 seconds.
        try {
            Thread.sleep(5000);
        }
        catch (InterruptedException e) {}

        // Resume execution of the thread.
        System.out.println(Thread.currentThread().getName() + ": Resuming the thread");
        t.resume();

        // Tell the main program to sleep for 5 seconds.
        try {
            Thread.sleep(5000);
        }
        catch (InterruptedException e) {}

        // Stop the thread.
        System.out.println(Thread.currentThread().getName() + ": Stopping the thread");
        t.stop();
    }
}
 

4. Animations

Here is an example of a simple animation.  We have used a separate thread to control the motion of a ball on the screen.  To view this applet, click here.
 

Animation.java

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

public class Animation extends Applet implements Runnable, ActionListener {
    int miFrameNumber = -1;
    int miTimeStep;
    Thread mAnimationThread;
    boolean mbIsPaused = false;
    Button mButton;
    Point mCenter;
    int miRadius;
    int miDX, miDY;

    public void init() {
        miTimeStep = 50; // Time step in milliseconds.

        // Create a button to start and stop the animation.
        mButton = new Button("Stop");
        add(mButton);
        mButton.addActionListener(this);

        // Initialize the parameters of the circle.
        mCenter = new Point(getSize().width/2, getSize().height/2);
        miRadius = 15;
        miDX = 4;  // X offset per timestep.
        miDY = 3;  // Y offset per timestep.
    }

    public void start() {
        if (mbIsPaused) {
            // Don't do anything.  The animation has been paused.
        } else {
            // Start animating.
            if (mAnimationThread == null) {
                mAnimationThread = new Thread(this);
            }
            mAnimationThread.start();
        }
    }

    public void stop() {
        // Stop the animating thread.
        if (mAnimationThread != null) {
            mAnimationThread.stop();
        }
        mAnimationThread = null;
    }

    public void actionPerformed(ActionEvent e) {
        if (mbIsPaused) {
            mbIsPaused = false;
            mButton.setLabel("Stop");
            start();
        } else {
            mbIsPaused = true;
            mButton.setLabel("Start");
            stop();
        }
    }

    public void run() {
        // Just to be nice, lower this thread's priority so it can't
        // interfere with other processing going on.
        Thread.currentThread().setPriority(Thread.MIN_PRIORITY);

        // Remember the starting time.
        long startTime = System.currentTimeMillis();

        // Remember which thread we are.
        Thread currentThread = Thread.currentThread();

        // This is the animation loop.
        while (currentThread == mAnimationThread) {
            // Advance the animation frame.
            miFrameNumber++;

            // Update the position of the circle.
            move();

            // Draw the next frame.
            repaint();

            // Delay depending on how far we are behind.
            try {
                startTime += miTimeStep;
                Thread.sleep(Math.max(0,
                             startTime-System.currentTimeMillis()));
            }
            catch (InterruptedException e) {
                break;
            }
        }
    }

    // Draw a frame.
    public void paint(Graphics g) {
        // Display the frame number.
        g.drawString("Frame " + miFrameNumber, getSize().width/2 - 40,
                            getSize().height - 15);

        // Draw the circle.
        g.setColor(Color.red);
        g.fillOval(mCenter.x-miRadius, mCenter.y-miRadius, 2*miRadius,
                       2*miRadius);
    }
 

    // Update the position of the circle.
    void move() {
        mCenter.x += miDX;
        if (mCenter.x - miRadius < 0 ||
            mCenter.x + miRadius > getSize().width) {
            miDX = -miDX;
            mCenter.x += 2*miDX;
        }

        mCenter.y += miDY;
        if (mCenter.y - miRadius < 0 ||
            mCenter.y + miRadius > getSize().height) {
            miDY = -miDY;
            mCenter.y += 2*miDY;
        }
    }
}