Threads - An Introduction

Introduction

We often require to break our application into a number of independent subtasks, called threads (threads of execution). We write each thread in an individual manner and assume that there is some mechanism for dividing up the CPU time and sharing it between these threads. Threads enhance performance and functionality, by allowing a program to perform multiple tasks simultaneously.

Why would we want to do this? Well, take the example of a web browser. It allows us to download a web page and at the same time move the window, and even scroll through the text reading what has already started downloading. Threading is supported directly in the Java language, allowing us to write platform independent threaded applications (There are some difficulties when dealing with the different operating systems as there are slight behavior differences).

Multiprocessing vs. Multithreading

Multiprocessing refers to multiple applications, executing 'apparently' concurrently, whereas multithreading refers to one or more tasks within an application executing 'apparently' concurrently. The word 'apparently' is used as the platform usually has a single CPU that must be shared. If there are multiple CPUs it is possible that processes might actually be executing concurrently.

Multiprocessing is a heavyweight process, under the control of the operating system. The different applications have no relationships with each other. Multithreading can produce programs that are very efficient, since work can be performed during low CPU actions, such as waiting for the user to enter data, or waiting for a file to load. Multithreaded programs can be concurrently executing in a multiprocessing system. They can exist concurrently with each other.

Why use threads?

Suppose we wish to write an application that performs a CPU-intensive operation. It is possible that this application will become unresponsive as the application ignores user input. For example, this counter applet simply counts forever in the textfield.

Figure 11.1, “Bad Counter Example” displays a capture of an applet that does not count correctly. The Bad Counter example. You can see it running here - BadCounter.html Note: This applet does not work correctly - It may be possible that your web browser could lock-up when you follow this link. You could open a new browser and drag the link into the new browser. It is worth following the link to see it working incorrectly.

Figure 11.1. Bad Counter Example

Bad Counter Example

The source code is as below and as in BadCounter.java. If you are unfamiliar with threads it should not be immediately obvious when looking at the code why this applet does not work.

 1 
 2 
 3   // An Unresposive Counting Applet - by Derek Molloy
 4 
 5   import java.applet.*;
 6   import java.awt.*;
 7   import java.awt.event.*;
 8 
 9   public class BadCounter extends Applet implements ActionListener
10   {
11 	private int count = 0;
12 	private Button start, stop;
13 	private TextField countField;
14 	private boolean running = true;
15 
16 	public void init() 
17 	{
18 	  countField = new TextField(10);
19 	  stop = new Button("Stop");
20 	  start = new Button("Start");
21 	  this.add(countField);
22 	  start.addActionListener(this);
23 	  this.add(start);
24 	  stop.addActionListener(this);
25 	  this.add(stop);
26 	}
27 
28 	public void go() 
29 	{
30 	  while (running) 
31 	  {
32 	    try 
33 	    { 
34 	      Thread.currentThread().sleep(100); 
35 	    } 
36 	    catch (InterruptedException e)
37 	    {  }  //do nothing
38 	    countField.setText(Integer.toString(count++));
39 	  }
40 	}
41 	
42 	public void actionPerformed(ActionEvent e) 
43 	{ 
44 	  if (e.getSource().equals(start))
45 	  {
46 	    this.go();	
47 	  }
48 	  else if (e.getSource().equals(stop))
49 	  {
50 	    this.running = !this.running;
51 	  }
52 	}
53   } 
54 
55 

So why does it not work? When you run the applet you will see that it starts counting as expected with a delay between each number - so what is wrong? Well if we look at the code step by step:

  • First off, the applet initialises itself, draws the buttons, textfield and is working perfectly. The applet is now waiting for a button to be pressed. No problems so far.

  • Next off, the "Start" button is pressed by the user. This event is sent to the BadCounter's actionPerformed() since we have added a listener to the "Start" button using the addActionListener(). No problems so far.

  • The actionPerformed() method calls go() and waits for a response. But this is where are problem is - the go() method has a loop - that says "while running is true sleep for a while (100ms) and then update the value in the countField TextField". Since this loop goes around forever control will never be returned to the actionPerformed() method.

  • The only thing that is happening at the moment is that the loop is looping - The applet has stopped listening for events as it is trapped within the loop, within go(), within the actionPerformed() method.

  • Event though the code is correct for the "Stop" button it can never be pressed - You will notice that in the running applet the buttons don't even go up and down when you press them.

  • We need some way of having a loop (infinite or other) and also listening for events - we need threads.

Threads - A better way?

We want to re-write the application in the previous section so that we can do this computationally intensive count operation, while still providing access to the user interface. This is not much different than most threaded applications, where we might define several threads:

  • One thread to handle the keyboard input.

  • One thread to handle a file load.

  • One thread to handle some complex computation.

The computation thread could be given priority once the load or input threads are blocked, waiting for communication from the keyboard or the disk drive. Sequential programs do not have this facility. They cannot perform any operations while the user is deciding what to type.

We have two ways of creating threads in Java. We can extend the Thread class, or we can implement the Runnable interface.

Extending the Thread class

The first way is to extend the Thread class to create our own Thread class, and then providing specific functionality by over-riding the run() method - such as:

 1 
 2 
 3 	public class MyThread extends Thread
 4 	{
 5 		public void run()
 6 		{
 7 			//add your implementation here
 8 		}
 9 	}
10 	
11 

Implementing the Runnable Interface

Implementing the Runnable intearface allows us more flexiblitiy than extending the Thread as we our new class is still able to inherit from any parent class, without that parent having to be Thread. The Runnable interface has one method that we must implement - run(). So our thread might look like:

 1 
 2 	public class MyThread extends WhateverClass implements Runnable
 3 	{
 4 		public void run()
 5 		{
 6 			//add your implementation here
 7 		}
 8 	}
 9 
10 

A Working Counter

This section fixes the counter applet discussed in the section called “Why use threads?” to allow it to work correctly. Figure 11.2, “Bad Counter Fixed Example” displays a capture of a working version of the applet. The Bad Counter Fixed example. You can see it running here - BadCounterFixed.html. You will notice that when you press the "Start" button the counter starts, but you are still able to press the "Stop" button and it does indeed stop the counter. I have not written the code to allow you to restart the counter - for simplicity.

Figure 11.2. Bad Counter Fixed Example

Bad Counter Fixed Example

The source code is as below and as in BadCounterFixed.java.

 1 
 2 
 3   // A Working Counting Applet - by Derek Molloy
 4 
 5   import java.applet.*;
 6   import java.awt.*;
 7   import java.awt.event.*;
 8 
 9   public class BadCounterFixed extends Applet implements ActionListener, 
10                                                  Runnable
11   {
12 	private int count = 0;
13 	private Button start, stop;
14 	private TextField countField;
15 	private boolean running = true;
16 	private Thread theThread;
17 
18 	public void init() 
19 	{
20 	  countField = new TextField(10);
21 	  this.add(countField);
22 
23 	  start = new Button("Start");
24 	  start.addActionListener(this);
25 	  this.add(start);
26 
27 	  stop = new Button("Stop");
28 	  stop.addActionListener(this);
29 	  this.add(stop);
30 
31 	  this.theThread = new Thread(this);
32 	}
33 
34 	public void run() 
35 	{
36 	  while (running) 
37 	  {
38 	    try 
39 	    { 
40 	      Thread.currentThread().sleep(100); 
41 	    } 
42 	    catch (InterruptedException e)
43 	    {  }  //do nothing
44 	    countField.setText(Integer.toString(count++));
45 	  }
46 	}
47 	
48 	public void actionPerformed(ActionEvent e) 
49 	{
50 	  if (e.getSource().equals(start))
51 	  {
52 	    this.theThread.start();
53 	  }
54 	  else if (e.getSource().equals(stop))
55 	  {
56 	    this.running = !this.running;
57 	  }
58 	}
59   } 
60 
61 

So why does this version work? Well I have made a few changes:

  • A new state is added to the class, theThread Thread object. This is our reference to the new thread object that we are about to create.

  • The BadCounterFixed class implements the Runnable interface, and so was required to write a run() method. This means that every class that implements Runnable must have written a run() method.

  • In the init() method we create an object for the theThread reference using new Thread(this);, which creates a new Thread object out of the current object this. The Thread constructor that we use is Thread(Runnable target). The only reason that we are allowed to pass this to the constructor is because this implements the Runnable interface.

  • Instead of calling go() in the previous case, this time the actionPerformed() method calls theThread.start() method when the "Start" button is pressed. Note that we call the start() method and NOT run() method that was just discussed. The call to start() asks the thread manager to invoke this thread's run() method 'when it gets the chance'. This means that when start() is called - control is returned immediately to the the next line of actionPerformed() and the thread manager invokes a separate thread of execution at the same time for the while(running) method.