A thread is a "lightweight" process, usually
with a shared data segment.
If that doesn't make sense, consider:
About Java threads:
(Window 95, NT and Solaris all have different behaviors).
To see why threads are useful, let us first look
at an example that does not use threads:
Here is the code:
(source file)
Note:
Exercise 12.1:
Modify the above code to add a third dog to the race.
You will also need to download
UniformRandom.java.
As you can see, it's a very crude depiction of a dog-race.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
class NewFrame extends JFrame {
// For drawing the race results.
JPanel drawingArea;
public NewFrame ()
{
// Frame properties.
this.setTitle ("Dog Race");
this.setResizable (true);
this.setSize (600,200);
Container cPane = this.getContentPane();
// cPane.setLayout (new BorderLayout());
// Quit button.
JPanel p = new JPanel ();
JButton quitb = new JButton ("QUIT");
quitb.addActionListener (
new ActionListener () {
public void actionPerformed (ActionEvent a)
{
System.exit (0);
}
}
);
p.add (quitb);
// Pressing "start" calls race()
JButton startb = new JButton ("START");
startb.addActionListener (
new ActionListener () {
public void actionPerformed (ActionEvent a)
{
race ();
}
}
);
p.add (startb);
// Now add the panel to the frame.
cPane.add (p, BorderLayout.SOUTH);
// A JPanel to draw the results.
drawingArea = new JPanel();
drawingArea.setBackground (Color.white);
cPane.add (drawingArea, BorderLayout.CENTER);
this.setVisible (true);
}
void race ()
{
Dimension D = drawingArea.getSize ();
// Finish-line is at the right end of the canvas.
int finishLine = D.width;
// Create two dog instances with different ID's.
Dog d1 = new Dog (1, drawingArea);
Dog d2 = new Dog (2, drawingArea);
// Each dog sleeps a random amount of time.
int d1Nextwakeup = (int) UniformRandom.uniform (300,600);
int d2Nextwakeup = (int) UniformRandom.uniform (300,600);
// Keep track of the current time.
int currentTime = 0;
// Stop when one dog crosses the finish line.
while ( (d1.position < finishLine) && (d2.position < finishLine) ) {
// See which one is done first.
if (d1Nextwakeup < d2Nextwakeup) {
// Dog d1 is first
try {
// Static method "sleep" in class Thread.
Thread.sleep (d1Nextwakeup - currentTime);
}
catch (InterruptedException e) {
System.out.println (e);
}
currentTime = d1Nextwakeup;
d1.move(); // Move a random distance.
d1Nextwakeup += (int) UniformRandom.uniform (300,600);
}
else {
// Dog d2 is first
try {
Thread.sleep (d2Nextwakeup - currentTime);
}
catch (InterruptedException e) {
System.out.println (e);
}
currentTime = d2Nextwakeup;
d2.move(); // Move a random distance.
d2Nextwakeup += (int) UniformRandom.uniform (300,600);
}
} // end-while
}
}
class Dog {
public int position = 20; // Starting position.
int ID; // An ID.
JPanel drawingArea; // The panel on which to draw.
public Dog (int ID, JPanel drawingArea)
{
this.ID = ID;
this.drawingArea = drawingArea;
// Draw ID on panel.
Graphics g = drawingArea.getGraphics ();
g.drawString (""+ID, 5, 20*ID+8);
}
public void move ()
{
// Move a random amount.
int newPosition = position + (int) UniformRandom.uniform (50,100);
// Draw new position.
Graphics g = drawingArea.getGraphics ();
int size = newPosition - position;
g.fillRect (position, 20*ID, size, 10);
position = newPosition;
}
}
public class Race1 {
public static void main (String[] argv)
{
NewFrame nf = new NewFrame ();
}
}
(This is done in the method race()).
Now we will look at a thread-version of the above dog-race program:
Here is the code using threads:
(source file)
Note:
Exercise 12.2:
Modify the above code to add a third dog to the race.
You will also need to download
UniformRandom.java.
When you run your code, note the following:
class NewFrame extends JFrame {
JPanel drawingArea;
public NewFrame ()
{
// ... same as in Race1.java
}
void race ()
{
Dimension D = drawingArea.getSize ();
// Finish-line is at the right end of the canvas.
int finishLine = D.width;
// Create two dog instances with different ID's.
Dog d1 = new Dog (1, drawingArea);
Dog d2 = new Dog (2, drawingArea);
// Create a Thread instance for each dog.
// Note: the class Dog must implement the
// Runnable interface.
Thread dThread1 = new Thread (d1);
Thread dThread2 = new Thread (d2);
// Start running the threads.
// ("start" is a method in Thread).
dThread1.start();
dThread2.start();
}
}
class Dog implements Runnable {
// ...
public Dog (int ID, JPanel drawingArea)
{
// ...
}
public void move ()
{
// ...
}
// Must implement this method to implement
// the Runnable interface.
public void run ()
{
// Compute the finish line distance.
int finishLine = drawingArea.getSize().width;
// While not complete...
while (position < finishLine) {
// Sleep
try {
Thread.sleep ((int)UniformRandom.uniform (300,600));
}
catch (InterruptedException e) {
System.out.println (e);
}
// Move.
move ();
}
}
}
public class Race2 {
// ...
}
Thread dThread1 = new Thread (d1);
dThread1.start();
class Dog extends Thread {
//..
// Override run()
public void run()
{
// ...
}
}
Dog d1 = new Dog (1, drawingArea);
d1.start();
To solve the problem of "losers" continuing the race,
we will have each thread read the status of the race
via a method call:
Here is the code:
(source file)
Note:
public boolean raceFinished (boolean set)
{
// If set==true, set the race to be over.
// Otherwise, return current status.
}
class NewFrame extends JFrame {
// ... same as in Race2.java ...
void race ()
{
Dimension D = drawingArea.getSize ();
// Finish-line is at the right end of the canvas.
int finishLine = D.width;
// Create two dog instances with different ID's.
Dog d1 = new Dog (1, drawingArea, this);
Dog d2 = new Dog (2, drawingArea, this);
// Create a Thread instance for each dog.
Thread dThread1 = new Thread (d1);
Thread dThread2 = new Thread (d2);
raceOver = false;
// Start running the threads.
dThread1.start();
dThread2.start();
}
boolean raceOver;
// The same method is called to check whether the race
// is complete or to indicate that it is complete.
public boolean raceFinished (boolean set)
{
boolean returnVal = false;
if (!set)
returnVal = raceOver;
else {
raceOver = true;
returnVal = raceOver;
}
return returnVal;
}
}
class Dog implements Runnable {
// ...
NewFrame f; // To find out whether the race is over.
public Dog (int ID, JPanel drawingArea, NewFrame f)
{
this.ID = ID;
this.drawingArea = drawingArea;
// Draw ID on canvas.
Graphics g = drawingArea.getGraphics ();
g.drawString (""+ID, 5, 20*ID+8);
// Save the frame reference.
this.f = f;
}
public void move ()
{
// ... same as in Race2.java ...
}
public void run ()
{
// Compute the finish line distance.
int finishLine = drawingArea.getSize().width;
// While not complete...
while (position < finishLine) {
try {
Thread.sleep ((int)UniformRandom.uniform (300,600));
}
catch (InterruptedException e) {
System.out.println (e);
}
// Check whether race is over.
if (f.raceFinished (false)) break;
// Move if race is still on.
move ();
}
if (position >= finishLine)
f.raceFinished (true);
}
}
// ...
Let us demonstrate a problem with the above code by
modifying the raceFinished() method:
(source file)
When executed, you will notice that both dogs go across
the finish line - thus, we have not solved the problem!
A look at the screen output shows why:
The problem is easy to solve:
Note:
public boolean raceFinished (boolean set, int ID)
{
boolean returnVal = raceOver;
System.out.println ("raceFinished(): startofcode: ID=" + ID + ", set=" + set);
if (!set)
returnVal = raceOver;
else {
// Race overseer sleeps for a while.
try {
Thread.sleep ((int)UniformRandom.uniform (1000,2000));
}
catch (InterruptedException e) {
System.out.println (e);
}
raceOver = true;
returnVal = true;
}
System.out.println ("raceFinished(): endofcode: ID=" + ID + ", set=" + set);
return returnVal;
}
Here's what we did:
raceFinished(): startofcode: ID=2, set=true
raceFinished(): startofcode: ID=1, set=false
raceFinished(): endofcode: ID=1, set=false
raceFinished(): startofcode: ID=1, set=false
raceFinished(): endofcode: ID=1, set=false
raceFinished(): startofcode: ID=1, set=true
raceFinished(): endofcode: ID=2, set=true
raceFinished(): endofcode: ID=1, set=true
Note:
Upon execution, the screen output shows that only one thread
executes inside the body of raceFinished():
public synchronized boolean raceFinished (boolean set, int ID)
{
// ... everything else stays the same ...
}
raceFinished(): startofcode: ID=2, set=false
raceFinished(): endofcode: ID=2, set=false
raceFinished(): startofcode: ID=2, set=true
raceFinished(): endofcode: ID=2, set=true
raceFinished(): startofcode: ID=1, set=false
raceFinished(): endofcode: ID=1, set=false
synchronized (SomeSynchObject) {
// Critical section
}
The above solution of calling raceFinished()
in the class NewFrame is not completely satisfactory:
We can use a static variable that will be shared across
all Dog instances:
(source file)
Exercise 12.3:
Download the above source,
and remove the keyword static in the declaration
of raceFinished(). The synchronization now does
not work. Why?
class Dog implements Runnable {
// ...
static boolean raceOver;
}
class Dog implements Runnable {
// ...
public static synchronized void startRace ()
{
raceOver = false;
}
public static synchronized boolean raceFinished (boolean set, int ID)
{
// ... same as before ...
}
}
Java provides a range of priorities for threads:
As an example, let us set different priorities for the two
dogs in the method race():
(source file)
When executed, the second dog clearly wins. The screen output
also indicates how often Dog 2 gets to execute.
Exercise 12.4:
Try using the old sleep range of 300-600 milliseconds.
Now Dog 2 does not have much of an advantage. Why?
void race ()
{
// ...
Thread dThread1 = new Thread (d1);
dThread1.setPriority (Thread.NORM_PRIORITY);
Thread dThread2 = new Thread (d2);
dThread2.setPriority (Thread.NORM_PRIORITY+3);
Dog.startRace();
// Start running the threads.
dThread1.start();
dThread2.start();
}
We will also decrease the sleep time to the range 3-6 milliseconds:
Thread.sleep ((int)UniformRandom.uniform (3,6));
Sometimes, threads need to coordinate on some activity:
The following is one approach to handling waiting and notification:
As an example, let us modify the dog race as follows:
To achieve this objective, we will take the following steps:
Here is the code:
(source file)
Note:
Exercise 12.5:
Try executing the above code. What do you observe?
// A monitor class with synchronized access.
class DogMonitor {
// Set the new position of Dog# ID.
public synchronized void setPosition (int ID, int newPosition)
{
}
// Get the current position of Dog# ID.
public synchronized int getPosition (int ID)
{
}
// Wait.
public synchronized void synchWait ()
{
}
// Tell the other dog that it can continue.
public synchronized void synchNotify ()
{
}
}
while (monitor.getPosition (ID) < finishLine) {
// Sleep.
// Move.
// If you need to wait, wait.
// If the other guy is waiting, notify.
}
class NewFrame extends JFrame {
// ...
public NewFrame ()
{
// ... same as before ...
}
void race ()
{
Dimension D = drawingArea.getSize ();
// Finish-line is at the right end of the canvas.
int finishLine = D.width;
// Create the dog monitor.
DogMonitor monitor = new DogMonitor (2);
// Create two dog instances with different ID's.
Dog d1 = new Dog (1, drawingArea, monitor, 2);
Dog d2 = new Dog (2, drawingArea, monitor, 1);
// Create a Thread instance for each dog.
Thread dThread1 = new Thread (d1);
Thread dThread2 = new Thread (d2);
// Start running the threads.
dThread1.start();
dThread2.start();
}
}
// A monitor class with synchronized access.
class DogMonitor {
// The position of each dog.
int[] position;
public DogMonitor (int numDogs)
{
position = new int [numDogs+1];
for (int i=1; i<=numDogs; i++)
position[i] = 20;
}
// Set the new position of Dog# ID.
public synchronized void setPosition (int ID, int newPosition)
{
position[ID] = newPosition;
}
// Get the current position of Dog# ID.
public synchronized int getPosition (int ID)
{
return position[ID];
}
// A waiting dog needs to call a synchronized
// wait method inside the monitor.
public synchronized void synchWait ()
{
try {
// wait() is inherited from Object.
wait ();
}
catch (InterruptedException e)
{
System.out.println (e);
}
}
// Tell the other dog that it can continue.
public synchronized void synchNotify ()
{
// notify() is inherited from Object.
notify ();
}
}
class Dog implements Runnable {
// ...
DogMonitor monitor; // Store a reference to the monitor.
int OtherID; // ID of the other dog.
public Dog (int ID, JPanel drawingArea, DogMonitor monitor, int OtherID)
{
// ... store parameters ...
}
public void move ()
{
// ... same as before ...
}
public void run ()
{
// Compute the finish line distance.
int finishLine = drawingArea.getSize().width;
// While not complete...
while (monitor.getPosition (ID) < finishLine) {
try {
Thread.sleep ((int)UniformRandom.uniform (300,600));
}
catch (InterruptedException e) {
System.out.println (e);
}
move ();
// Check if you need to wait.
int other = monitor.getPosition (OtherID);
int mine = monitor.getPosition (ID);
if (mine - other > 20) {
System.out.println ("ID=" + ID + " waiting: mine=" + mine
+ ", other=" + other);
monitor.synchWait ();
System.out.println ("ID=" + ID + " continuing");
}
// See if the other guy is waiting for me.
if (other - mine <= 20) {
System.out.println ("ID=" + ID + " notifying: mine=" + mine
+ ", other=" + other);
monitor.synchNotify();
}
} // endwhile
}
}
public class Race9 {
// ...
}
Then, no other thread can execute any synchronized
method in DogMonitor, even if the method
is different.
public synchronized void synchWait ()
{
try {
// wait() is inherited from Object.
wait ();
}
catch (InterruptedException e)
{
System.out.println (e);
}
}
It is possible for the above
code to deadlock.
Java does not detect deadlock for you and leaves it to you
to worry about.
There are two ways to handle deadlock:
We will provide a simple way around deadlock: always make
sure no-one else is waiting when you call wait():
(source file)
Note:
// A monitor class with synchronized access.
class DogMonitor {
int[] position;
boolean[] isWaiting; // A waiting dog sets a value here.
int numDogs;
public DogMonitor (int numDogs)
{
this.numDogs = numDogs;
position = new int [numDogs+1];
for (int i=1; i<=numDogs; i++)
position[i] = 20;
isWaiting = new boolean [numDogs+1];
for (int i=1; i<=numDogs; i++)
isWaiting [i] = false;
}
// Set the new position of Dog# ID.
public synchronized void setPosition (int ID, int newPosition)
{
position[ID] = newPosition;
}
// Get the current position of Dog# ID.
public synchronized int getPosition (int ID)
{
return position[ID];
}
// Before waiting, make sure no-one else waits.
public synchronized void synchWait (int ID)
{
boolean someoneWaiting = false;
for (int i=1; i<=numDogs; i++)
if (isWaiting[i]) {
System.out.println ("synchWait: i=" + i + " is waiting");
someoneWaiting= true;
}
if (someoneWaiting) {
notifyAll();
for (int i=1; i<=numDogs; i++)
isWaiting[i] = false;
}
try {
isWaiting[ID] = true;
wait ();
}
catch (InterruptedException e)
{
System.out.println (e);
}
}
public synchronized void synchNotify ()
{
notify ();
}
}
while (monitor.getPosition (ID) < finishLine) {
try {
Thread.sleep ((int)UniformRandom.uniform (300,600));
}
catch (InterruptedException e) {
System.out.println (e);
}
move ();
// Check if you need to wait.
int other = monitor.getPosition (OtherID);
int mine = monitor.getPosition (ID);
if (mine - other > 20) {
System.out.println ("ID=" + ID + " waiting: mine=" + mine
+ ", other=" + other);
monitor.synchWait (ID);
System.out.println ("ID=" + ID + " continuing");
}
// "other" AND "mine" COULD HAVE CHANGED BY NOW!
// See if the other guy is waiting for me.
if (other - mine <= 20) {
System.out.println ("ID=" + ID + " notifying: mine=" + mine
+ ", other=" + other);
monitor.synchNotify();
}
} // endwhile
while (monitor.getPosition (ID) < finishLine) {
try {
Thread.sleep ((int)UniformRandom.uniform (300,600));
}
catch (InterruptedException e) {
System.out.println (e);
}
move ();
// Check if you need to wait.
if (monitor.getPosition (ID)
- monitor.getPosition (OtherID) > 20) {
monitor.synchWait (ID);
}
// See if the other guy is waiting for me.
if (monitor.getPosition (OtherID)
- monitor.getPosition (ID) <= 20) {
monitor.synchNotify(ID);
}
} // endwhile
Sometimes it is desirable to bundle threads into
a group for convenient "group" management.
This can be achieved quite easily using a ThreadGroup
instance:
CAUTION about stopping and starting threads:
As an example, we will include two additional buttons in
our dog-race:
To build safe suspend/resume:
interface SafeRunnable extends Runnable {
public abstract void safeStop ();
public abstract void safeResume ();
public abstract void safeSuspend ();
}
The idea is: a thread handles its own resume/suspend behavior.
class Dog implements SafeRunnable {
// ...
// New variables:
boolean isAlive = true; // For safeStop
boolean isSuspended = false; // For safeResume/Suspend
// ....
public void run ()
{
// ...
while ( (isAlive) && (position < finishLine) ) {
try {
Thread.sleep ((int)UniformRandom.uniform (300,600));
// Check whether suspended.
checkSuspended();
}
catch (InterruptedException e) {
System.out.println (e);
}
}
// ...
}
// ...
public synchronized void safeStop ()
{
isAlive = false;
}
public synchronized void safeSuspend ()
{
isSuspended = true;
}
public synchronized void safeResume ()
{
isSuspended = false;
this.notify();
}
private synchronized void checkSuspended ()
{
try {
while ( (isSuspended) && (isAlive) )
// Block while suspended.
this.wait();
}
catch (InterruptedException e) {
System.out.println (e);
}
}
}
class SafeThreadGroup {
Vector safeThreads = new Vector();
public void addThread (SafeRunnable s)
{
safeThreads.addElement (s);
}
public void safeStop ()
{
Enumeration e = safeThreads.elements();
while (e.hasMoreElements()) {
SafeRunnable s = (SafeRunnable) e.nextElement();
s.safeStop();
}
}
public void safeResume ()
{
Enumeration e = safeThreads.elements();
while (e.hasMoreElements()) {
SafeRunnable s = (SafeRunnable) e.nextElement();
s.safeResume();
}
}
public void safeSuspend ()
{
Enumeration e = safeThreads.elements();
while (e.hasMoreElements()) {
SafeRunnable s = (SafeRunnable) e.nextElement();
s.safeSuspend();
}
}
}
class NewFrame extends JFrame {
// ...
public NewFrame ()
{
// ...
JButton pauseb = new JButton ("PAUSE");
pauseb.addActionListener (
new ActionListener () {
public void actionPerformed (ActionEvent a)
{
suspendRace ();
}
}
);
p.add (pauseb);
JButton continueb = new JButton ("CONTINUE");
continueb.addActionListener (
new ActionListener () {
public void actionPerformed (ActionEvent a)
{
continueRace ();
}
}
);
p.add (continueb);
// ...
}
SafeThreadGroup dogGroup;
void race ()
{
// ...
// Create two dog instances with different ID's.
Dog d1 = new Dog (1, drawingArea);
Dog d2 = new Dog (2, drawingArea);
// Create a ThreadGroup instance.
dogGroup = new SafeThreadGroup ();
// Create a Thread instance for each dog.
// Note: the class Dog must implement the
// Runnable interface.
Thread dThread1 = new Thread (d1);
Thread dThread2 = new Thread (d2);
// Add the threads to the thread group.
dogGroup.addThread (d1);
dogGroup.addThread (d2);
Dog.startRace();
// Start running the threads.
dThread1.start();
dThread2.start();
}
public void suspendRace ()
{
dogGroup.safeSuspend ();
}
public void continueRace ()
{
dogGroup.safeResume ();
}
}
A few comments about threads: