Difference between revisions of "CSC352 Java Threads: Producer-Consumer Lab"
(→Processing GUI version) |
(→Version 2: A Threaded Producer of Rectangles) |
||
Line 116: | Line 116: | ||
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. | 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: | + | The version below is an ill-formed (you'll have to fix it!) first attempt at doing just that. First the Eclipse version: |
<br /> | <br /> | ||
Line 189: | Line 189: | ||
<br /> | <br /> | ||
+ | And now the Processing-IDE version: | ||
+ | |||
<br /> | <br /> | ||
+ | <source lang="java"> | ||
+ | // 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(); | ||
+ | } | ||
+ | </source> | ||
<br /> | <br /> | ||
<br /> | <br /> | ||
+ | {| style="width:100%; background:silver" | ||
+ | |- | ||
+ | | | ||
+ | |||
+ | == Question 1 == | ||
+ | |} | ||
+ | [[Image:QuestionMark3.jpg|right|120px]] | ||
+ | |||
+ | * 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. | ||
+ | |||
+ | 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()'''; | ||
+ | } | ||
+ | |||
+ | |||
<br /> | <br /> | ||
<br /> | <br /> |
Revision as of 22:05, 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.
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();
}