8.1 An Introduction to Threads


Threads - An Introduction

Introduction

We often require to break our application into a number of independent sub-tasks, 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 interact with 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. This is the case with more recent Intel processors, such as the i3, i5 and i7 where there are multiple cores.

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 application simply counts forever, outputting the count in a Textfield component.

Figure 8.1, “The Bad Counter Application Running” displays a capture of an application that does not work correctly. You can press the Start button to start the count but the Stop button will not work to stop the application from counting.

Note: This application does not work correctly - It may even have to be killed by using your task manager. Under Windows press CTRL-SHIFT-ESC together to open the task manager.


Figure 8.1 The Bad Counter Application Running


Here is the source code for this example. If you have not studied threads before then it will not be immediately obvious why this application does not work correctly.


12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758
/** Bad Counter Application Example - Derek Molloy, Dublin City University
* This code will not work correctly and the question is why?
* I correct this in the next version.
*/

package ee402;

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

@SuppressWarnings("serial")
public class BadCounterApp extends Frame implements ActionListener {

        private int count = 0;
        private Button start, stop;
        private TextField countText;
        private boolean running = true;
        
        public BadCounterApp(){
                super("Bad Counter");
                this.setLayout(new FlowLayout());
                this.countText = new TextField(10);
                this.add(countText);
                this.start = new Button("Start");
                this.add(start);
                this.start.addActionListener(this);
                this.stop = new Button("Stop");
                this.add(stop);
                this.stop.addActionListener(this);
                this.pack();
                this.setVisible(true);
        }        
        
        public void go(){
                while(running){
                        this.countText.setText("Count: " + count++);
                        try{
                                Thread.sleep(100);
                        }
                        catch(InterruptedException e){
                                System.out.println("Thread was Interrupted!");
                        }
                }
        }
        
        public static void main(String[] args) {
                new BadCounterApp();
        }

        public void actionPerformed(ActionEvent e) {
                if (e.getSource().equals(start)){
                        this.go();
                }
                else if (e.getSource().equals(stop)){
                        this.running = false;
                }
        }
}

So why does it not work? When you run the application 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 application creates an instance of Frame, draws the buttons, textfield and is working perfectly. The application 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 countText TextField". Since this loop goes around forever control will never be returned to the actionPerformed() method from the call to go().

  • The only thing that is happening at the moment is that the loop is looping - The application has stopped listening for events as it is trapped within the loop, within the call to the go() method, 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 application 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 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 flexibility 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 8.2, “The Bad Counter Fixed Application Running” displays a capture of a working version of the application. 

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 at this point. 

Figure 8.2 The Bad Counter Fixed Application Running


The source code is below, with the main changes highlighted in yellow:


1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859
/** Bad Counter Fixed Application Example - Derek Molloy, Dublin City University
* This code fixes the previous non-working version by adding threading
*/

package ee402;

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

@SuppressWarnings("serial")
public class BadCounterAppFixed extends Frame implements ActionListener, Runnable {

        private int count = 0;
        private Button start, stop;
        private TextField countText;
        private boolean running = true;
        private Thread thread;
        
        public BadCounterAppFixed(){
                super("Bad Counter Fixed");
                this.setLayout(new FlowLayout());
                this.countText = new TextField(10);
                this.add(countText);
                this.start = new Button("Start");
                this.add(start);
                this.start.addActionListener(this);
                this.stop = new Button("Stop");
                this.add(stop);
                this.stop.addActionListener(this);
                this.pack();
                this.setVisible(true);
                this.thread = new Thread(this);
        }        
        
        public void run(){
                while(running){
                        this.countText.setText("Count: " + count++);
                        try{
                                Thread.sleep(100);
                        }
                        catch(InterruptedException e){
                                System.out.println("Thread was Interrupted!");
                        }
                }
        }
        
        public static void main(String[] args) {
                new BadCounterAppFixed();
        }

        public void actionPerformed(ActionEvent e) {
                if (e.getSource().equals(start)){
                        this.thread.start();
                }
                else if (e.getSource().equals(stop)){
                        this.running = false;
                }
        }
}

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

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

  • The BadCounterAppFixed 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 thread 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 class 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.


These notes are copyright Dr. Derek Molloy, School of Electronic Engineering, Dublin City University, Ireland 2013-present. Please contact him directly before reproducing any of the content in any way.
Comments