1.124 Lecture 15 10/31/2000

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)

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)

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

Note: The following methods in the java.lang.Thread class should no longer be used, since they can lead to unpredicable behavior: stop(), suspend() and resume().

The following example illustrates various thread states.  The main thread in our program creates a new thread, Thread-0.  It then starts Thread-0, thereby making Thread-0 runnable so that it prints out an integer every 500 milliseconds.  We call the sleep() method to enforce the 500 millisecond delay between printing two consecutive integers.  In the meantime, the main thread proceeds to print out an integer every second only.  The output from the program shows that the two threads are running in parallel.  When the main thread finishes its for loop, it stops Thread-0.

We maintain a variable, myThread, which initially references Thread-0.  This variable is polled by the run() method to make sure that it is still referencing Thread-0.  All we have to do to stop the thread is to set myThread to null.  This will cause the run() method to terminate normally.

class MyClass implements Runnable {
    int i;
    Thread myThread;

    public MyClass() {
        i = 0;
    }

    // This will terminate the run() method.
    public void stop() {
        myThread = null;
    }

    // The run() method simply prints out a sequence of integers, one every half second.
    public void run() {
        // Get a handle on the thread that we are running in.
        myThread = Thread.currentThread();

        // Keep going as long as myThread is the same as the current thread.
        while (Thread.currentThread() == myThread) {
            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 another thread.  This thread will run in parallel to the main thread.
        System.out.println(Thread.currentThread().getName() + ": Starting a separate thread");
        t.start();

        // The main thread proceeds to print out a sequence of integers of its own, one every second.
        for (int i = 0; i < 6; i++) {
            System.out.println(Thread.currentThread().getName() + ": " + i);
            // Tell the main thread to sleep for a second.
            try {
                Thread.sleep(1000);
            }
            catch (InterruptedException e) {}
        }

        // Stop the parallel thread.  We do this by setting myThread to null in our runnable object.
        System.out.println(Thread.currentThread().getName() + ": Stopping the thread");
        a.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.

anim.html

<HTML>
<BODY>
<APPLET CODE="Animation.class" WIDTH=300 HEIGHT=400>
</APPLET>
</BODY>
 

Animation.java

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class Animation extends JApplet 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() {
        // Make the animation run at 20 frames per second.  We do this by
        // setting the timestep to 50ms.
        miTimeStep = 50;

        // 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.

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

        // Create a JPanel subclass and add it to the JApplet.  All drawing
        // will be done here, do we must write the paintComponent() method.
        // Note that the anonymous class has access to the private data of
        // class Animation, because it is defined locally.
        getContentPane().add(new JPanel() {
            public void paintComponent(Graphics g) {
                // Paint the background.
                super.paintComponent(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);
            }
        }, "Center");
    }

    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 by setting the mAnimationThread variable
        // to null.  This will cause the thread to break out of the while loop,
        // so that the run() method terminates naturally.
        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;
            }
        }
    }

    // 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;
        }
    }
}