Threading becomes tricky when threads perform operations
that can conflict with each other. For example, say that we face
the following situation.
An object of class Filler
wants to put a number into the Cavity
attribute of an instance of class Box.
It can only do this when the cavity is empty.
Class
Getter wants to retrieve the number from Cavity
and then leave the Cavity
empty.
Ideally, Filler
and Getter
would alternate their calls to the methods put()
and get().
However, if no special steps are taken, it is quite
easy for Getter
to call get()
when the Cavity
is empty and for Filler
to call put()
when the Cavity
is still full.
This type of situation is called a data race
because each thread is racing to do its task without waiting for
the other thread to finish its activity.
A synchronization scheme is used to prevent
this problem. The solution is to make the threads wait in single
file at the method or code block where the conflict can occur.
In the case above, this means that an instance
of Box only
allows one thread at a time to invoke its put()
or get().
It is as if only one thread object owns the key
to the lock on the doors to Box.
It must give up the key (though by tradition you say that the thread
gives up the lock) before any other thread can access a synchronized
method on the object.
Note that each instance has its
own lock. There is no interference problem among different instances
of Box.
A lock on one instance has no affect on access to put()
and get()on
another instance of box.
Synchronized Methods
When a process invokes put()
or get(),
which have the synchronized
modifier, for an instance of the Box
class below, it will wait until it is granted the lock for that
object. The process continues through the method. However, if a
flag is not set it will invoke the wait()
method. This allows the process to give up the lock and remain at
that point in the computation until a notifyAll()
method is called.
The notifyAll()
will wake up all the currently waiting threads and check if they
can have the lock for this particular object. If one process gets
the lock then it proceeds though the method and then invokes notifyAll(),
which will wake up any other waiting threads.
ExclusiveApplet.java
+ Box.java
+ Getter.java
+ Filler.java
+ Outputable.java
|
import
javax.swing.*;
import java.awt.*;
import java.awt.event.*;
/**
* This demo applet illustrates synchronization
with the
* Filler thread trying to fill the bin in the
Box object
* and the Getter thread trying to get the value
from the
* bin. The Filler must wait if the bin already
is full while
* the getter must wait if it is empty.
*
* Output of the thread goes to the text area
on the applet
* interface. The applet implements the Outputable
interface.
*
* Derived from Java Tutorial Cubbyhole/Producer
example.
**/
public class ExclusiveApplet extends JApplet
implements Outputable, ActionListener
{
// A Swing textarea for display of string info
JTextArea fTextArea = null;
/**
* Create a User Interface with a
textarea with sroll bars
* and a Go button to initiate processing
and a Clear button
* to clear the textarea.
**/
public void init () {
Container content_pane = getContentPane ();
JPanel panel = new JPanel (new
BorderLayout ());
// Create a text area.
fTextArea = new JTextArea ();
// Make it editable
fTextArea.setEditable (false);
// Add to a scroll pane so that
a long list of
// computations can be seen.
JScrollPane area_scroll_pane = new
JScrollPane (fTextArea);
panel.add (area_scroll_pane,BorderLayout.CENTER);
JButton go_button = new JButton ("Go");
go_button.addActionListener (this);
JButton clear_button = new JButton ("Clear");
clear_button.addActionListener (this);
JPanel control_panel = new JPanel ();
control_panel.add (go_button);
control_panel.add (clear_button);
panel.add (control_panel,BorderLayout.SOUTH);
// Add text area & control panel.
content_pane.add (panel);
} // init
/** Respond to the buttons to start the threads
or to clear
* the text area.
**/
public void actionPerformed (ActionEvent
e) {
if ( e.getActionCommand ().equals ("Go"))
start ();
else
fTextArea.setText (null);
} // actionPerformed
/** Create Filler and Getter thread instances
and
* start them filling and getting
from a Box instance.
**/
public void start () {
Box b = new Box (this);
Filler f1 = new Filler (b);
Getter b1 = new Getter (b);
f1.start ();
b1.start ();
} // start
/** Overided Outputable println to send string
to text area.**/
public void println (String str) {
fTextArea.append (str
+ CR);
}
/** Overided Outputable print to send string
to text area.**/
public void print (String str) {
fTextArea.append (str);
}
}// class ExclusiveApplet
|
/**
* Use synchronized methods to "put" and "fill"
* a bin, which holds some number passed to it.
* A flag indicates whether the bin is in a filled
state
* or not.
* The put method goes into wait state if bin
is filled.
* Similarly, the get method waits if bin not
filled.
* When they emerge from the wait states, the
processes
* do their given tasks and then invoke notifyAll
() to
* wake up other threads that might be waiting
on the
* object.
*
**/
public class Box {
private int fBin;
private boolean fFilled = false;
Outputable fOut;
/** Constructor obtains reference to Outputable
object.**/
Box (Outputable out){
fOut = out;
}
/**
* If bin is not filled, wait for
it to be.
* If bin filled, then empty it (i.e.
set flag to false.)
* Notify other waiting threads that
are in wait state
* on this object. Return the value
in the bin.
**/
public synchronized int get () {
while (fFilled == false){
try {
wait ();
}
catch (InterruptedException
e) { }
}
fFilled = false;
fOut.println ("Get value: " + fBin);
notifyAll ();
return fBin;
} // get
/**
* If bin is filled, wait for it
to be emptied.
* If bin not filled, then fill it (i.e.
set flag to true.)
* Notify other waiting threads that
are in wait state
* on this object.
**/
public synchronized void put (int value){
while (fFilled == true)
{
try {
wait ();
} catch (InterruptedException
e) { }
}
fBin = value;
fFilled = true;
fOut.println ("Put value: " + fBin);
notifyAll ();
} // put
} // class Box
|
/** Retrieve
the value from the Box object.**/
public class Getter extends Thread {
private Box fBox;
private int fNumber;
public Getter (Box b) {
fBox = b;
} // ctor
public void run () {
for (int i = 0; i < 10; i++) {
fNumber
= fBox.get ();
}
} // run
} // class Getter
|
/**
* A thread to fill the bin in the Box object.
* Derived from Java Tutorial Cubbyhole/Producer
example
**/
public class Filler extends Thread {
private Box fBox;
public Filler (Box b) {
fBox = b;
} // ctor
public void run () {
for (int i=0; i<10; i++){
fBox.put (i);
try {
sleep
( (int) (Math.random () * 100));
}
catch (InterruptedException
e) { }
}
} // run
} // class Filler
|
The output of this program shows that the filler and
getter threads do not get into a data race.
References & Web Resources
Latest update: Nov. 6, 2004
|