CSC352 Java Threads: Producer-Consumer Lab

From dftwiki3
Revision as of 22:27, 18 September 2013 by Thiebaut (talk | contribs)
Jump to: navigation, search

--D. Thiebaut (talk) 21:10, 18 September 2013 (EDT)


The goal of this lab is to see how threads can be used along size a main application that is already threaded. A perfect example of this is a Processing application where the draw() method is called by a thread that is timed to run approximately 30 times a second (or whatever interval is specified by the frameRate() method).




Version 1: Displaying 30 random rectangles a second

  • The following Java program is a Processing app. taylored for Eclipse. The version that follows is the one for the Processing IDE. They are both similar, but in one case the application is included in a class extending the PApplet class of Processing. In the second case, the Processing GUI hides the PApplet implementation.
  • Please refer to the tutorials at http://cs.smith.edu/dftwiki/index.php/Tutorials#Processing_and_Eclipse if you are interested in running Processing applets from Eclipse.


Eclipse-Ready version


// MainApplet.java
// D. Thiebaut
// This application needs the core.jar library of the Processing package to be included in the 
// build path of the application.   See 
import java.util.ArrayList;
import java.util.Random;
import processing.core.PApplet;

public class MainApplet extends PApplet {
	ArrayList<Rect> rects = new ArrayList<Rect>();
	Random generator = new Random( System.currentTimeMillis() );

	class Rect {
		int x; int y; int w; int h;
		int col;
		Rect() { this.x = this.y = 0; this.w = this.h = 10; col= 0xff6699cc; }
		Rect( int x, int y, int w, int h, int c ) { this.x = x; this.y = y; this.w = w; this.h = h; col = c; }
		public Rect randomRect() {
			return new Rect( generator.nextInt( width ), generator.nextInt( height ),
							 generator.nextInt( width ), generator.nextInt( height ),
							 generator.nextInt( 0x77ffffff ) );
		}
	}
	
	public void setup() {
		size( 600, 400 );
		smooth();
		frameRate( 10 );   // draw() will be called 10 times a second
	}
	
	public void draw() {
		Rect r = getNewRect();
		stroke( 0x000000 );
		fill( r.col );
		rect( r.x, r.y, r.w, r.h );
	}
	
	private Rect getNewRect() {
		return (new Rect()).randomRect();
	}
}


Processing GUI version


// MainApplet.sketch
// D. Thiebaut
import java.util.ArrayList;
import java.util.Random;

ArrayList<Rect> rects = new ArrayList<Rect>();
Random generator = new Random( System.currentTimeMillis() );

class Rect {
    int x; int y; int w; int h;
    int col;
    Rect() { this.x = this.y = 0; this.w = this.h = 10; col= 0xff6699cc; }
    Rect( int x, int y, int w, int h, int c ) { this.x = x; this.y = y; this.w = w; this.h = h; col = c; }
    public Rect randomRect() {
      return new Rect( generator.nextInt( width ), generator.nextInt( height ),
               generator.nextInt( width ), generator.nextInt( height ),
               generator.nextInt( 0x77ffffff ) );
    }
}
  
void setup() {
    size( 600, 400 );
    smooth();
    frameRate( 10 ); // draw() will be called 10 times a second
}
  
void draw() {
    Rect r = getNewRect();
    stroke( 0x000000 );
    fill( r.col );
    rect( r.x, r.y, r.w, r.h );
}
  
Rect getNewRect() {
    return (new Rect()).randomRect();
}
  • Implement one of the two versions above on your computer
  • Observe that it runs and displays translucent rectangles at a rate of 10 rectangles a second (approximately)


Version 2: A Threaded Producer of Rectangles


What we have is nice, but the rectangles are produced at a fixed rate of 10 rectangles a second. Assume that we want to generate them faster. We could increase the frame-rate, but this would take us only so far. Maybe 60 times a second at most.

Another option is to have a thread generate random rectangles, and have it pass them to draw() as it generates them.

This new thread will be a producer of rectangles, and draw() will be its consumer. In a first step we will make them exchange 1 rectangle at a time.

The version below is an ill-formed (you'll have to fix it!) first attempt at doing just that. First the Eclipse version:


// MainApplet2.java
// Another badly synchronized program in need of
// some help!
import java.util.ArrayList;
import java.util.Random;
import processing.core.PApplet;

public class MainApplet2 extends PApplet {
	ArrayList<Rect> rects = new ArrayList<Rect>();
	Random generator = new Random( System.currentTimeMillis() );
	Rect newRect = null;
	RectProducer producer;
	
	class Rect {
		int x; int y; int w; int h;
		int col;
		Rect() { this.x = this.y = 0; this.w = this.h = 10; col= 0xff6699cc; }
		Rect( int x, int y, int w, int h, int c ) { this.x = x; this.y = y; this.w = w; this.h = h; col = c; }
		public Rect randomRect() {
			return new Rect( generator.nextInt( width ), generator.nextInt( height ),
							 generator.nextInt( width ), generator.nextInt( height ),
							 generator.nextInt( 0x77ffffff ) );
		}
	}
	
	class RectProducer extends Thread {
		public void run() {
			// forever... (bad infinite loop, but ok for example)
			for (;;) {
				// wait for newRect to be absorbed by draw()
				while ( newRect != null )
					try {
						sleep( 1 ); // wait 1 ms
					} catch (InterruptedException e) {}
				
				// put new randomly generated rect in newRect
				newRect = (new Rect()).randomRect();
			}
		}
	}
	
	public void setup() {
		size( 600, 400 );
		smooth();
		frameRate( 10 );
		
		//--- create a producer of rectangles ---
		producer = new RectProducer();
		producer.start();
	}
	
	public void draw() {
		if ( newRect == null )
			return;
		
		stroke( 0x000000 );
		fill( newRect.col );
		rect( newRect.x, newRect.y, newRect.w, newRect.h );
		newRect = null;
	}
	
	private Rect getNewRect() {
		return (new Rect()).randomRect();
	}
}


And now the Processing-IDE version:


  // MainApplet2.java
  // Another badly synchronized program in need of
  // some help!
  import java.util.ArrayList;
  import java.util.Random;
    ArrayList<Rect> rects = new ArrayList<Rect>();
    Random generator = new Random( System.currentTimeMillis() );
    Rect newRect = null;
    RectProducer producer;
    
    class Rect {
      int x; int y; int w; int h;
      int col;
      Rect() { this.x = this.y = 0; this.w = this.h = 10; col= 0xff6699cc; }
      Rect( int x, int y, int w, int h, int c ) { this.x = x; this.y = y; this.w = w; this.h = h; col = c; }
      public Rect randomRect() {
        return new Rect( generator.nextInt( width ), generator.nextInt( height ),
                 generator.nextInt( width ), generator.nextInt( height ),
                 generator.nextInt( 0x77ffffff ) );
      }
    }
    
    class RectProducer extends Thread {
      public void run() {
        // forever... (bad infinite loop, but ok for example)
        for (;;) {
          // wait for newRect to be absorbed by draw()
          while ( newRect != null )
            try {
              sleep( 1 ); // wait 1 ms
            } catch (InterruptedException e) {}
          
          // put new randomly generated rect in newRect
          newRect = (new Rect()).randomRect();
        }
      }
    }
    
    void setup() {
      size( 600, 400 );
      smooth();
      frameRate( 10 );
      
      //--- create a producer of rectangles ---
      producer = new RectProducer();
      producer.start();
    }
    
    void draw() {
      if ( newRect == null )
        return;
      
      stroke( 0x000000 );
      fill( newRect.col );
      rect( newRect.x, newRect.y, newRect.w, newRect.h );
      newRect = null;
    }
    
    private Rect getNewRect() {
      return (new Rect()).randomRect();
    }



Question 1

QuestionMark3.jpg
  • Not protecting the access to an object by two threads with some form of locks is really not a good idea. Make the code above robust by using wait() and notify() to help the two threads schedule themselves around the production of rectangles. Below is a brief explanation of how wait() and notify() typically work.


The typical way to use wait() is illustrated below:


    synchronized( someObject ) {
        while ( some condition is not met ) {
            try {
                someObject.wait();
            } catch (InterruptedException e) {}
        }
    }

It is normally used in a synchronized section, and while some condition is not met (for example the previously generated random rectangle hasn't been drawn on the screen yet, then the thread remains in a waiting state, waiting to be notified by some other thread.

The typical way to use notify() is illustrated below:


    synchronized( someObject ) {
        if  ( some condition is met ) {
            // do some work and consume something (say, a rectangle)
        }
        someObect.notify();
    }



Question 2

QuestionMark4.jpg


Instead of forcing the producer thread to create only one rectangle at a time, let's make it create as many as it can and put them into a FIFO for the consuming draw() thread.
We'll first use an unlimited (only by memory available, that is) queue: a ConcurrentLinkedQueue.

The typical way to use such a queue is demonstrated below:

Queue<someObject> queue = new ConcurrentLinkedQueue<someObject>();

// to add a new object to the end of the queue:
queue.add( object );

// to test if the queue is empty:
if ( queue.isEmpty() ) {
   // do something
}

// to remove an object from the head of the queue
object = queue.poll();













...