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 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.
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.
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.
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.
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
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
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.
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.
© 2006
Dr. Derek Molloy
(DCU).