Difference between revisions of "CSC352 Java Threads: Producer-Consumer Lab"
m (Thiebaut moved page CSC352 Java Threads: Producer-Consumer to CSC352 Java Threads: Producer-Consumer Lab) |
|||
Line 295: | Line 295: | ||
<br /> | <br /> | ||
<br /> | <br /> | ||
+ | {| style="width:100%; background:silver" | ||
+ | |- | ||
+ | | | ||
+ | |||
+ | == Question 2 == | ||
+ | |} | ||
+ | [[Image:QuestionMark4.jpg|right|120px]] | ||
+ | |||
<br /> | <br /> | ||
+ | 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. | ||
<br /> | <br /> | ||
+ | We'll first use an unlimited (only by memory available, that is) queue: a [http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ConcurrentLinkedQueue.html 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(); | ||
+ | |||
+ | |||
<br /> | <br /> | ||
<br /> | <br /> | ||
Line 306: | Line 333: | ||
<br /> | <br /> | ||
<br /> | <br /> | ||
+ | <br /> | ||
+ | <onlydft> | ||
+ | =Solution to Question 1= | ||
+ | <br /> | ||
+ | <source lang="java"> | ||
+ | // MainApplet3.java | ||
+ | // Another badly synchronized program in need of | ||
+ | // some help! | ||
+ | import java.util.ArrayList; | ||
+ | import java.util.Random; | ||
+ | |||
+ | import processing.core.PApplet; | ||
+ | |||
+ | |||
+ | public class MainApplet3 extends PApplet { | ||
+ | ArrayList<Rect> rects = new ArrayList<Rect>(); | ||
+ | Random generator = new Random( System.currentTimeMillis() ); | ||
+ | Rect newRect = null; | ||
+ | Object lock = new Object(); | ||
+ | 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() | ||
+ | synchronized( lock ) { | ||
+ | while ( newRect != null ) { | ||
+ | try { | ||
+ | lock.wait(); | ||
+ | } 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(); | ||
+ | newRect = null; // make sure the producer is the one starting the exchange | ||
+ | producer.start(); | ||
+ | } | ||
+ | |||
+ | public void draw() { | ||
+ | synchronized( lock ) { | ||
+ | if ( newRect != null ) { | ||
+ | stroke( 0x000000 ); | ||
+ | fill( newRect.col ); | ||
+ | rect( newRect.x, newRect.y, newRect.w, newRect.h ); | ||
+ | newRect = null; | ||
+ | lock.notify(); // wake up the producer if its waiting to produce a | ||
+ | // new rectangle. | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | |||
+ | } | ||
+ | |||
+ | </source> | ||
+ | <br /> | ||
+ | </onlydft> | ||
<br /> | <br /> | ||
[[Category:CSC352]][[Category:Labs]] | [[Category:CSC352]][[Category:Labs]] |
Revision as of 22:24, 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).
Contents
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 |
- 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 |
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();