Difference between revisions of "CSC352 Java Threads: Producer-Consumer Lab"
(→Question 1) |
(→Question 3) |
||
(11 intermediate revisions by the same user not shown) | |||
Line 9: | Line 9: | ||
<br /> | <br /> | ||
<br /> | <br /> | ||
− | =Version 1: Displaying | + | =Version 1: Displaying 10 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. | * 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. | ||
Line 26: | Line 26: | ||
public class MainApplet extends PApplet { | public class MainApplet extends PApplet { | ||
− | |||
Random generator = new Random( System.currentTimeMillis() ); | Random generator = new Random( System.currentTimeMillis() ); | ||
Line 67: | Line 66: | ||
// MainApplet.sketch | // MainApplet.sketch | ||
// D. Thiebaut | // D. Thiebaut | ||
− | + | ||
import java.util.Random; | import java.util.Random; | ||
− | + | ||
Random generator = new Random( System.currentTimeMillis() ); | Random generator = new Random( System.currentTimeMillis() ); | ||
Line 295: | Line 294: | ||
<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 /> | ||
+ | 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: | ||
+ | |||
+ | import java.util.Queue; | ||
+ | import java.util.concurrent.ConcurrentLinkedQueue; | ||
+ | |||
+ | 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 /> | ||
+ | Go at it and make your producer and consumer exchange rectangles via the queue. You may experience some strange behavior. If you do, try to figure it out, or see if you have been as clever with your implementation of '''draw()''' as you could have been... | ||
+ | <br /> | ||
+ | <br /> | ||
+ | {| style="width:100%; background:silver" | ||
+ | |- | ||
+ | | | ||
+ | |||
+ | == Question 3 == | ||
+ | |} | ||
+ | [[Image:QuestionMark5.jpg|right|120px]] | ||
+ | |||
+ | <br /> | ||
+ | You will have very likely noticed that the previous program hangs as it attempts to display rectangles. This is due to the difference in speed between the producer (very fast) and the consumer (very slow--remember that it runs only 10 times a second!). So we can limit the producer by giving it a queue with a fixed size, and blocking it whenever it wants to add a new rect in a full queue. | ||
+ | |||
+ | Let's use an [http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ArrayBlockingQueue.html ArrayBlockingQueue] with a small size, say 10, to hold the rectangles. | ||
+ | |||
+ | To create such a size-limited queue, use this code: | ||
+ | |||
+ | import java.util.concurrent.BlockingQueue; | ||
+ | |||
+ | |||
+ | BlockingQueue<''SomeObject''> queue = new ArrayBlockingQueue<''SomeObject''>( 10 ); | ||
+ | // keep the size of th queue small to start with! | ||
+ | |||
+ | To add another object to the queue as long as it's not full, or to block if it's full, do this: | ||
+ | |||
+ | |||
+ | try { | ||
+ | queue.put( ''object'' ); // blocks if queue if full | ||
+ | } catch (InterruptedException e) { } | ||
+ | |||
+ | Finally, to remove an object if the queue is not empty: | ||
+ | |||
+ | if ( !queue.isEmpty() ) | ||
+ | SomeObject object = queue.poll(); | ||
+ | |||
+ | <br /> | ||
+ | Go ahead and re-code your program and use a limited-size, blocking queue. | ||
+ | |||
+ | * Does it work better? | ||
+ | * Does it hang? | ||
+ | * Why? | ||
+ | * What can you do to improve the rapid display of information? | ||
<br /> | <br /> | ||
<br /> | <br /> | ||
Line 304: | Line 378: | ||
<br /> | <br /> | ||
<br /> | <br /> | ||
+ | <onlydft> | ||
+ | =Solution to Question 1= | ||
<br /> | <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 /> | ||
+ | =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 ); | ||
+ | } | ||
+ | |||
+ | } | ||
+ | |||
+ | </source> | ||
+ | <br /> | ||
+ | A better version of the '''draw()''' method is given below. However, on some runs the applet hangs, which is due to the difference in speed between producer and consumer. | ||
+ | <br /> | ||
+ | <source lang="java"> | ||
+ | public void draw() { | ||
+ | stroke( 0x000000 ); | ||
+ | while ( !queue.isEmpty() ) { | ||
+ | Rect newRect = queue.poll(); | ||
+ | fill( newRect.col ); | ||
+ | rect( newRect.x, newRect.y, newRect.w, newRect.h ); | ||
+ | } | ||
+ | } | ||
+ | </source> | ||
+ | <br /> | ||
+ | =Solution to Question 3= | ||
+ | <br /> | ||
+ | <source lang="java"> | ||
+ | // MainApplet4.java | ||
+ | import java.util.ArrayList; | ||
+ | import java.util.Queue; | ||
+ | import java.util.Random; | ||
+ | import java.util.concurrent.ArrayBlockingQueue; | ||
+ | import java.util.concurrent.BlockingQueue; | ||
+ | import java.util.concurrent.ConcurrentLinkedQueue; | ||
+ | |||
+ | import processing.core.PApplet; | ||
+ | |||
+ | |||
+ | public class MainApplet5 extends PApplet { | ||
+ | Random generator = new Random( System.currentTimeMillis() ); | ||
+ | RectProducer producer; | ||
+ | BlockingQueue<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(); | ||
+ | try { | ||
+ | queue.put( newRect ); // blocks if queue if full | ||
+ | } catch (InterruptedException e) { } | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | |||
+ | public void setup() { | ||
+ | size( 600, 400 ); | ||
+ | smooth(); | ||
+ | frameRate( 10 ); | ||
+ | |||
+ | //--- create a producer of rectangles --- | ||
+ | producer = new RectProducer(); | ||
+ | queue = new ArrayBlockingQueue<Rect>( 10 ); | ||
+ | producer.start(); | ||
+ | } | ||
+ | |||
+ | public void draw() { | ||
+ | stroke( 0x000000 ); | ||
+ | while ( !queue.isEmpty() ) { | ||
+ | Rect newRect = queue.poll(); | ||
+ | fill( newRect.col ); | ||
+ | rect( newRect.x, newRect.y, newRect.w, newRect.h ); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | } | ||
+ | </source> | ||
<br /> | <br /> | ||
+ | </onlydft> | ||
<br /> | <br /> | ||
[[Category:CSC352]][[Category:Labs]] | [[Category:CSC352]][[Category:Labs]] |
Latest revision as of 13:04, 21 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 10 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 {
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.Random;
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:
import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; 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();
Go at it and make your producer and consumer exchange rectangles via the queue. You may experience some strange behavior. If you do, try to figure it out, or see if you have been as clever with your implementation of draw() as you could have been...
Question 3 |
You will have very likely noticed that the previous program hangs as it attempts to display rectangles. This is due to the difference in speed between the producer (very fast) and the consumer (very slow--remember that it runs only 10 times a second!). So we can limit the producer by giving it a queue with a fixed size, and blocking it whenever it wants to add a new rect in a full queue.
Let's use an ArrayBlockingQueue with a small size, say 10, to hold the rectangles.
To create such a size-limited queue, use this code:
import java.util.concurrent.BlockingQueue; BlockingQueue<SomeObject> queue = new ArrayBlockingQueue<SomeObject>( 10 ); // keep the size of th queue small to start with!
To add another object to the queue as long as it's not full, or to block if it's full, do this:
try { queue.put( object ); // blocks if queue if full } catch (InterruptedException e) { }
Finally, to remove an object if the queue is not empty:
if ( !queue.isEmpty() ) SomeObject object = queue.poll();
Go ahead and re-code your program and use a limited-size, blocking queue.
- Does it work better?
- Does it hang?
- Why?
- What can you do to improve the rapid display of information?