Difference between revisions of "CSC352 Java Threads: Producer-Consumer Lab"

From dftwiki3
Jump to: navigation, search
Line 406: Line 406:
 
}
 
}
 
}
 
}
 +
}
 +
 +
}
 +
 +
</source>
 +
 +
<br />
 +
=Solution to Question 2 =
 +
<br />
 +
Note that this solution hangs.  The reason is that the producer is much faster at producing rectangles than the producer is at displaying them, and the queue grows too fast, hogging up the memory.
 +
<br />
 +
A better solution is to let the '''draw()''' thread loop on emptying the queue and keep on displaying rects as long as it finds one in the queue.    This will give the queue a chance not to grow so fast.
 +
<br />
 +
<source lang="java">
 +
// MainApplet4.java
 +
import java.util.ArrayList;
 +
import java.util.Queue;
 +
import java.util.Random;
 +
import java.util.concurrent.ConcurrentLinkedQueue;
 +
 +
import processing.core.PApplet;
 +
 +
 +
public class MainApplet4 extends PApplet {
 +
ArrayList<Rect> rects = new ArrayList<Rect>();
 +
Random generator = new Random( System.currentTimeMillis() );
 +
RectProducer producer;
 +
Queue<Rect> queue;
 +
 +
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 (;;) {
 +
Rect newRect = (new Rect()).randomRect();
 +
queue.add( newRect );
 +
}
 +
}
 +
}
 +
 +
public void setup() {
 +
size( 600, 400 );
 +
smooth();
 +
frameRate( 10 );
 +
 +
//--- create a producer of rectangles ---
 +
producer = new RectProducer();
 +
queue = new ConcurrentLinkedQueue<Rect>(); // make sure the producer is the one starting the exchange
 +
producer.start();
 +
}
 +
 +
public void draw() {
 +
if ( queue.isEmpty() )
 +
return;
 +
Rect newRect = queue.poll();
 +
stroke( 0x000000 );
 +
fill( newRect.col );
 +
rect( newRect.x, newRect.y, newRect.w, newRect.h );
 
}
 
}
 
 

Revision as of 21:27, 18 September 2013

--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();













...