In the previous exclusivity
case, multiple threads try to access an object and can
step on each other if not synchronized.
Here we look at an even trickier situation where
one thread needs to access data in another thread and also
avoid data race conditions.
The standard example is similar to the producer/consumer
paradigm mentioned previously but rather than using a third
object, such as the Box
in the exclusivity example,
to exchange data, the consumer must grab data directly from
the producer.
The producer needs time to make whatever it
is to give to the consumer. The producer object can block
the consumer by invoking one of its own synchronized method.
So for example, the producer class could have a synchronized
produce()
method that is invoked to create data for the consumer. The
consumer call, say, a synchronized get()
method on the producer object to obtain the data, but this
will block until the process invokign the produce()
method gives up the lock.
Remember, blocking occurs
on an object. If any synchronized method on the object is
invoked, all synchronized methods will block on that object
until the process returns from the synchronized method.
That is, the producer locks its own door to
the consumer until it has data ready. Similarly, when the
consumer is getting the data from the producer, it obtains
the lock and thus prevents the producer from generating more
data until the consumer is finished.
Sensor
& DataGetter
Below we show this paradigm in which the Sensor
class represents the producer
thread and DataGetter
represents the consumer thread.
An instance of Sensor
obtains its data (here just clock readings) in a loop in run()
via calls to the synchronized sense()
method. The data goes into a buffer array. An outside thread
can call the get()
method to obtain the oldest data in the buffer. (The indices
are set up to emulate a FIFO (First-In-First-Out) buffer.)
When the buffer is full, the Sensor
thread waits (that is, gives up the lock by calling the wait()
method) for data to be read out.
To read out the data, a DataGetter
instance invokes the synchronized get()
method in the Sensor
instance. If no new data is available, it will give up the
lock and wait for new data to appear (that is, when notifyAll()
is invoked in the sense()
method).
DataSyncApplet.java
+ Sensor.java
+ DataGetter.java
+ Outputable.java
|
import
javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
/**
* Simulate a situation where
a sensor puts data into a buffer
* and a data reader picks data from the
"bottom" of the buffer.
**/
public class DataSyncApplet 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 a sensor and use a DataGetter
to obtain its readings.**/
public void start (){
// Create the sensor and
start it
Sensor s = new Sensor (this);
s.start ();
// Create DataGetter and
tell it to obtain
// 100 sensor readings.
DataGetter dg = new DataGetter
(s, 100, this);
dg.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 DataSyncApplet
|
import
java.util.*;
/**
* This class represents a sensor producing
data
* that the DataGetter objects want to read.
**/
public class Sensor extends Thread{
// Size of the data buffer.
private static final int BUFFER_SIZE = 10;
// Don't let data production get more than
// 8 values ahead of the DataGetter
private static final int MAXGAP = 8;
private String [] fBuffer;
private int fBufIndex = 0; //
sensor data buffer index
private int fGetIndex = 0; //
data reading index
private final long fStart = System.currentTimeMillis
();
boolean fFlag = true;
Outputable fOutput = null;
/** Constructor gets reference to the Outputable
object and creates
* a buffer for the data.
**/
Sensor (Outputable out) {
fOutput = out;
fBuffer = new String [BUFFER_SIZE];
} // ctor
/** Turn off sensor readings. **/
public void stopData (){
fFlag = false;
}
/** Measure the parameter of interest in
a loop. **/
public void run (){
while (fFlag) sense ();
}
/** Use clock readings to simulate data.
**/
private final String simulateData (){
return "" + (int) (System.currentTimeMillis
() - fStart);
}
/**
* Use indices fBufIndex,
fGetIndex, and the lag () method
* to implement a
first-in-first-out (FIFO) buffer.
**/
synchronized void sense () {
// Don't add more to the data
buffer until the fGetIndex
// has reached within
the allow range of fBufIndex.
while (lag () > MAXGAP) {
try
{
wait ();
}
catch
(Exception e) {}
}
fBuffer[fBufIndex] = simulateData
();
fOutput.println ("Sensor["+
(fBufIndex)+"] = "
+ fBuffer[fBufIndex]);
// Increment index to next
slot for new data
fBufIndex++;
// Circle back to bottom of
array if reaches top
if ( fBufIndex == BUFFER_SIZE)
fBufIndex = 0;
notifyAll ();
} // sense
/** Calculate distance the DataGetter is
running behind
* the production
of data.
**/
int lag () {
int dif = fBufIndex - fGetIndex;
if (dif < 0) dif += BUFFER_SIZE;
return dif;
}
// Get a data reading from the buffer. **/
synchronized String get () {
// When indices are equal,
wait for new data.
while (fBufIndex == fGetIndex)
{
try{
wait ();
}
catch (Exception
e){}
}
notifyAll ();
// Get data at current index
String data = fBuffer[fGetIndex];
// Increment pointer of next
datum to get.
fGetIndex++;
// Circle back to bottom of
array if reaches top
if (fGetIndex == BUFFER_SIZE)
fGetIndex = 0;
return data;
} // get
} // class Sensor |
/** This class obtains sensor data via the get () method.
* To simulate radom accesses to the sensor,
it will
* sleep for brief periods of different lengths
after
* every access.
*
* After the data is obtained, this thread
will stop the
* sensor thread before it finishes.
**/
public class DataGetter extends Thread {
Sensor fSensor = null;
Outputable fOutput = null;
int fMaxData = 1000;
int fDataCount = 0;
/** Constructor gets reference to the sensor
and the Outputable
* object and also gets the number
of data readings to make.
**/
DataGetter (Sensor sensor, int maxNum,
Outputable
out) {
fSensor = sensor;
fMaxData = maxNum;
fOutput = out;
} // ctor
/** Loop over sensor data readings until
data limit reached. **/
public void run () {
Random r = new Random ();
while (true) {
String data_value
= fSensor.get ();
fOutput.println
(fDataCount++ + ". Got: " + data_value );
// Stop both threads
if data taking finished.
if (fDataCount >=
fMaxData){
fSensor.stopData
();
break;
}
// Pause briefly
before access the data again.
try{
sleep
(r.nextInt () % 300);
}
catch (Exception
e){}
}
} // run
} // class DataGetter |
References &
Web Resources
Latest update: Nov. 6, 2004
|