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
(Solution):
Modify the above code to add a third dog to the race.
You will also need to download
UniformRandom.java.
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
(Solution):
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:
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:
Let us demonstrate a problem with the above code by
modifying the race_finished() 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:
The above solution of calling race_finished()
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
(Solution):
Download the above source,
and remove the keyword static in the declaration
of race_finished(). The synchronization now does
not work. Why?
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
(Solution):
Try using the old sleep range of 300-600 milliseconds.
Now Dog 2 does not have much of an advantage. Why?
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
(Solution):
Try executing the above code. What do you observe?
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:
NOTE: this section was written for Java 1.1. Many
of the methods used are now deprecated. Refer to the current
Swing version of this lecture for an updated version.
Sometimes it is desirable to bundle threads into
a group for convenient "group" management.
This can be achieved quite easily using a ThreadGroup
instance:
As an example, we will include two additional buttons in
our dog-race:
Here is the code:
(source file)
Note:
A few comments about threads:
As you can see, it's a very crude depiction of a dog-race.
import java.awt.*;
import java.awt.event.*;
class NewFrame extends Frame {
Canvas c;
public NewFrame ()
{
// Frame properties.
this.setTitle ("Dog Race");
this.setBackground (Color.cyan);
this.setResizable (true);
this.setSize (600,200);
// Quit button.
Panel p = new Panel ();
Button quitb = new Button ("QUIT");
quitb.addActionListener (
new ActionListener () {
public void actionPerformed (ActionEvent a)
{
System.exit (0);
}
}
);
p.add (quitb);
// Pressing "start" calls race()
Button startb = new Button ("START");
startb.addActionListener (
new ActionListener () {
public void actionPerformed (ActionEvent a)
{
race ();
}
}
);
p.add (startb);
this.add (p, BorderLayout.SOUTH);
// A canvas to draw the results.
c = new Canvas();
c.setBackground (Color.white);
this.add (c, BorderLayout.CENTER);
this.setVisible (true);
}
void race ()
{
Dimension D = c.getSize ();
// Finish-line is at the right end of the canvas.
int finish_line = D.width;
// Create two dog instances with different ID's.
Dog d1 = new Dog (1, c);
Dog d2 = new Dog (2, c);
// Each dog sleeps a random amount of time.
int d1_nextwakeup = (int) UniformRandom.uniform (300,600);
int d2_nextwakeup = (int) UniformRandom.uniform (300,600);
// Keep track of the current time.
int current_time = 0;
// Stop when one dog crosses the finish line.
while ( (d1.position < finish_line) && (d2.position < finish_line) ) {
// See which one is done first.
if (d1_nextwakeup < d2_nextwakeup) {
try {
// Static method "sleep" in class Thread.
Thread.sleep (d1_nextwakeup - current_time);
}
catch (InterruptedException e) {
System.out.println (e);
}
current_time = d1_nextwakeup;
d1.move(); // Move a random distance.
d1_nextwakeup += (int) UniformRandom.uniform (300,600);
}
else {
try {
Thread.sleep (d2_nextwakeup - current_time);
}
catch (InterruptedException e) {
System.out.println (e);
}
current_time = d2_nextwakeup;
d2.move(); // Move a random distance.
d2_nextwakeup += (int) UniformRandom.uniform (300,600);
}
}
}
}
class Dog {
public int position = 20; // Starting position.
int ID; // An ID.
Canvas c; // The canvas on which to draw.
public Dog (int ID, Canvas c)
{
this.ID = ID;
this.c = c;
// Draw ID on canvas.
Graphics g = c.getGraphics ();
g.drawString (""+ID, 5, 20*ID+8);
}
public void move ()
{
// Move a random amount.
int new_position = position + (int) UniformRandom.uniform (50,100);
// Draw new position.
Graphics g = c.getGraphics ();
int size = new_position - position;
g.fillRect (position, 20*ID, size, 10);
position = new_position;
}
}
public class Race1 {
public static void main (String[] argv)
{
NewFrame nf = new NewFrame ();
}
}
(This is done in the method race()).
The same example with threads
class NewFrame extends Frame {
Canvas c;
public NewFrame ()
{
// ... same as in Race1.java ...
}
void race ()
{
Dimension D = c.getSize ();
// Finish-line is at the right end of the canvas.
int finish_line = D.width;
// Create two dog instances with different ID's.
Dog d1 = new Dog (1, c);
Dog d2 = new Dog (2, c);
// Create a Thread instance for each dog.
// Note: the class Dog must implement the
// Runnable interface.
Thread d1_thread = new Thread (d1);
Thread d2_thread = new Thread (d2);
// Start running the threads.
// ("start" is a method in Thread).
d1_thread.start();
d2_thread.start();
}
}
class Dog implements Runnable {
// ... same as in Race1.java ...
public Dog (int ID, Canvas c)
{
// ... same as in Race1.java ...
}
public void move ()
{
// ... same as in Race1.java ...
}
// Must implement this method to implement
// the Runnable interface.
public void run ()
{
// Compute the finish line distance.
int finish_line = c.getSize().width;
// While not complete...
while (position < finish_line) {
// Sleep
try {
Thread.sleep ((int)UniformRandom.uniform (300,600));
}
catch (InterruptedException e) {
System.out.println (e);
}
// Move.
move ();
}
}
}
public class Race2 {
// ... same as in Race1.java ...
}
Thread d1_thread = new Thread (d1);
d1_thread.start();
class Dog extends Thread {
//..
// Override run()
public void run()
{
// ...
}
}
Dog d1 = new Dog (1, c);
d1.start();
Sharing data across threads
public boolean race_finished (boolean set)
{
// If set==true, set the race to be over.
// Otherwise, return current status.
}
class NewFrame extends Frame {
// ... same as in Race2.java ...
void race ()
{
Dimension D = c.getSize ();
// Finish-line is at the right end of the canvas.
int finish_line = D.width;
// Create two dog instances with different ID's.
Dog d1 = new Dog (1, c, this);
Dog d2 = new Dog (2, c, this);
// Create a Thread instance for each dog.
// Note: the class Dog must implement the
// Runnable interface.
Thread d1_thread = new Thread (d1);
Thread d2_thread = new Thread (d2);
race_over = false;
// Start running the threads.
// ("start" is a method in Thread).
d1_thread.start();
d2_thread.start();
}
boolean race_over;
// The same method is called to check whether the race
// is complete or to indicate that it is complete.
public boolean race_finished (boolean set)
{
boolean return_val = false;
if (!set)
return_val = race_over;
else {
race_over = true;
return_val = race_over;
}
return return_val;
}
}
class Dog implements Runnable {
// ...
NewFrame f; // To find out whether the race is over.
public Dog (int ID, Canvas c, NewFrame f)
{
this.ID = ID;
this.c = c;
// Draw ID on canvas.
Graphics g = c.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 finish_line = c.getSize().width;
// While not complete...
while (position < finish_line) {
try {
Thread.sleep ((int)UniformRandom.uniform (300,600));
}
catch (InterruptedException e) {
System.out.println (e);
}
// Check whether race is over.
if (f.race_finished (false)) break;
// Move if race is still on.
move ();
}
if (position >= finish_line)
f.race_finished (true);
}
}
// ...
Synchronized access to shared data.
public boolean race_finished (boolean set, int ID)
{
boolean return_val = race_over;
System.out.println ("finished: startofcode: ID=" + ID + ", set=" + set);
if (!set)
return_val = race_over;
else {
// Race overseer sleeps for a while.
try {
Thread.sleep ((int)UniformRandom.uniform (1000,2000));
}
catch (InterruptedException e) {
System.out.println (e);
}
race_over = true;
return_val = true;
}
System.out.println ("race_fin: endofcode: ID=" + ID + ", set=" + set);
return return_val;
}
Here's what we did:
finished: startofcode: ID=2, set=true
finished: startofcode: ID=1, set=false
finished: endofcode: ID=1, set=false
finished: startofcode: ID=1, set=false
finished: endofcode: ID=1, set=false
finished: startofcode: ID=1, set=true
finished: endofcode: ID=2, set=true
finished: endofcode: ID=1, set=true
Note:
Upon execution, the screen output shows that only one thread
executes inside the body of race_finished():
public synchronized boolean race_finished (boolean set, int ID)
{
// ... everything else stays the same ...
}
finished: startofcode: ID=2, set=false
finished: endofcode: ID=2, set=false
finished: startofcode: ID=2, set=true
finished: endofcode: ID=2, set=true
finished: startofcode: ID=1, set=false
finished: endofcode: ID=1, set=false
synchronized (SomeSynchObject) {
// Critical section
}
Using static variables to share data.
class Dog implements Runnable {
// ...
static boolean race_over;
}
class Dog implements Runnable {
// ...
public static synchronized void start_race ()
{
race_over = false;
}
public static synchronized boolean race_finished (boolean set, int ID)
{
// ... same as before ...
}
}
Thread priorities
void race ()
{
// ...
Thread d1_thread = new Thread (d1);
d1_thread.setPriority (Thread.NORM_PRIORITY);
Thread d2_thread = new Thread (d2);
d2_thread.setPriority (Thread.NORM_PRIORITY+3);
Dog.start_race();
// Start running the threads.
// ("start" is a method in Thread).
d1_thread.start();
d2_thread.start();
}
We will also decrease the sleep time to the range 3-6 milliseconds:
Thread.sleep ((int)UniformRandom.uniform (3,6));
Waiting and notifying
// A monitor class with synchronized access.
class DogMonitor {
// Set the new position of Dog# ID.
public synchronized void set_position (int ID, int new_position)
{
}
// Get the current position of Dog# ID.
public synchronized int get_position (int ID)
{
}
// Wait.
public synchronized void synch_wait ()
{
}
// Tell the other dog that it can continue.
public synchronized void synch_notify ()
{
}
}
while (monitor.get_position (ID) < finish_line) {
// Sleep.
// Move.
// If you need to wait, wait.
// If the other guy is waiting, notify.
} // endwhile
class NewFrame extends Frame {
// ...
public NewFrame ()
{
// ... same as before ...
}
void race ()
{
Dimension D = c.getSize ();
// Finish-line is at the right end of the canvas.
int finish_line = D.width;
// Create the dog monitor.
DogMonitor monitor = new DogMonitor (2);
// Create two dog instances with different ID's.
Dog d1 = new Dog (1, c, monitor, 2);
Dog d2 = new Dog (2, c, monitor, 1);
// Create a Thread instance for each dog.
// Note: the class Dog must implement the
// Runnable interface.
Thread d1_thread = new Thread (d1);
Thread d2_thread = new Thread (d2);
// Start running the threads.
// ("start" is a method in Thread).
d1_thread.start();
d2_thread.start();
}
}
// A monitor class with synchronized access.
class DogMonitor {
// The position of each dog.
int[] position;
public DogMonitor (int num_dogs)
{
position = new int [num_dogs+1];
for (int i=1; i<=num_dogs; i++)
position[i] = 20;
}
// Set the new position of Dog# ID.
public synchronized void set_position (int ID, int new_position)
{
position[ID] = new_position;
}
// Get the current position of Dog# ID.
public synchronized int get_position (int ID)
{
return position[ID];
}
// A waiting dog needs to call a synchronized
// wait method inside the monitor.
public synchronized void synch_wait ()
{
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 synch_notify ()
{
// 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, Canvas c, DogMonitor monitor, int OtherID)
{
// ... store parameters ...
}
public void move ()
{
// ... same as before ...
}
public void run ()
{
// Compute the finish line distance.
int finish_line = c.getSize().width;
// While not complete...
while (monitor.get_position (ID) < finish_line) {
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.get_position (OtherID);
int mine = monitor.get_position (ID);
if (mine - other > 20) {
System.out.println ("ID=" + ID + " waiting: mine=" + mine
+ ", other=" + other);
monitor.synch_wait ();
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.synch_notify();
}
} // endwhile
}
}
public class Race9 {
// ...
}
Then, no other thread can execute any synchronized
method in DogMonitor, even if the method
is different.
public synchronized void synch_wait ()
{
try {
// wait() is inherited from Object.
wait ();
}
catch (InterruptedException e)
{
System.out.println (e);
}
}
Deadlock
// A monitor class with synchronized access.
class DogMonitor {
int[] position;
boolean[] is_waiting; // A waiting dog sets a value here.
int num_dogs;
public DogMonitor (int num_dogs)
{
this.num_dogs = num_dogs;
position = new int [num_dogs+1];
for (int i=1; i<=num_dogs; i++)
position[i] = 20;
is_waiting = new boolean [num_dogs+1];
for (int i=1; i<=num_dogs; i++)
is_waiting [i] = false;
}
// Set the new position of Dog# ID.
public synchronized void set_position (int ID, int new_position)
{
position[ID] = new_position;
}
// Get the current position of Dog# ID.
public synchronized int get_position (int ID)
{
return position[ID];
}
// Before waiting, make sure no-one else waits.
public synchronized void synch_wait (int ID)
{
boolean someone_waiting = false;
for (int i=1; i<=num_dogs; i++)
if (is_waiting[i]) {
System.out.println ("synch_wait: i=" + i + " is waiting");
someone_waiting= true;
}
if (someone_waiting) {
notifyAll();
for (int i=1; i<=num_dogs; i++)
is_waiting[i] = false;
}
try {
is_waiting[ID] = true;
wait ();
}
catch (InterruptedException e)
{
System.out.println (e);
}
}
public synchronized void synch_notify ()
{
notify ();
}
}
while (monitor.get_position (ID) < finish_line) {
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.get_position (OtherID);
int mine = monitor.get_position (ID);
if (mine - other > 20) {
System.out.println ("ID=" + ID + " waiting: mine=" + mine
+ ", other=" + other);
monitor.synch_wait (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.synch_notify();
}
} // endwhile
while (monitor.get_position (ID) < finish_line) {
try {
Thread.sleep ((int)UniformRandom.uniform (300,600));
}
catch (InterruptedException e) {
System.out.println (e);
}
move ();
// Check if you need to wait.
if (monitor.get_position (ID)
- monitor.get_position (OtherID) > 20) {
monitor.synch_wait (ID);
}
// See if the other guy is waiting for me.
if (monitor.get_position (OtherID)
- monitor.get_position (ID) <= 20) {
monitor.synch_notify(ID);
}
} // endwhile
Thread groups
The methods suspend() and resume()
of ThreadGroup can be used to suspend or resume
the execution of threads in the group.
class NewFrame extends Frame {
// ...
public NewFrame ()
{
// ...
// A "pause" button.
Button pauseb = new Button ("PAUSE");
pauseb.addActionListener (
new ActionListener () {
public void actionPerformed (ActionEvent a)
{
suspend_race ();
}
}
);
p.add (pauseb);
// A "continue" button.
Button continueb = new Button ("CONTINUE");
continueb.addActionListener (
new ActionListener () {
public void actionPerformed (ActionEvent a)
{
continue_race ();
}
}
);
p.add (continueb);
// ...
}
// ...
ThreadGroup DogGroup;
void race ()
{
Dimension D = c.getSize ();
// Finish-line is at the right end of the canvas.
int finish_line = D.width;
// Create two dog instances with different ID's.
Dog d1 = new Dog (1, c);
Dog d2 = new Dog (2, c);
// Create a ThreadGroup instance.
// Note: a String must be passed as an ID.
DogGroup = new ThreadGroup ("All dogs");
// Create a Thread instance for each dog.
Thread d1_thread = new Thread (DogGroup, d1);
Thread d2_thread = new Thread (DogGroup, d2);
Dog.start_race();
// Start running the threads.
d1_thread.start();
d2_thread.start();
}
public void suspend_race ()
{
DogGroup.suspend ();
}
public void continue_race ()
{
DogGroup.resume ();
}
}
DogGroup = new ThreadGroup ("All dogs");
Thread d1_thread = new Thread (DogGroup, d1);
Summary