Home : Course Map : Chapter 8 : Java :
Demo: Talking Threads
JavaTech
Course Map
Chapter 8

Introduction
Threads Overview
  Demo 1   Demo 2
Stopping Threads
Multi-Processing
Thread Tasks
Animations
 
 Demo 3   Demo 4  
  Demo 5

Non-interacting
  Demo 6

Task Splitting
  Demo 7

Exclusivity
  Demo 8

Communicating
  Demo 9

Priority/Scheduling
More Thread

Exercises

    Supplements
Java2D Animation
  Demo 1 
Processor View
More Concurrency
Cloning
  Demo 2  Demo 3 

     About JavaTech
     Codes List
     Exercises
     Feedback
     References
     Resources
     Tips
     Topic Index
     Course Guide
     What's New

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

              Tech
Timers
  Demo 1
Hist. Adapt Range
  Demo 2
Sorting in Java
  Demo 3
Histogram Median
  Demo 4
Refactoring
  Demo 5
Error Bars
  Demo 6
Exercises

           Physics
Least Squares Fit
  Demo 1
Fit to Polynomial
  Demo 2
Fit Hist Errors
  Demo 3
Discretization
  Demo 4
Timing
  Demo 5
Exercises

  Part I Part II Part III
Java Core 1  2  3  4  5  6  7  8  9  10  11  12 13 14 15 16 17
18 19 20
21
22 23 24
Supplements

1  2  3  4  5  6  7  8  9  10  11  12

Tech 1  2  3  4  5  6  7  8  9  10  11  12
Physics 1  2  3  4  5  6  7  8  9  10  11  12

Java is a trademark of Sun Microsystems, Inc.