Background:
One of Java's powers is its robust and flexible mutithreading (multitasking) ability. The Producer/Consumer simulation in computer science addresses the problem of concurrent access to data by multiple processes. This causes what is called a "race condition", which is why I chose to make this simulation about race cars and a tire factory.
In the simulation running below, a tire factory (the producer) makes tires at a rate that varies by a small random time factor (to account for differences in shipping time). The race cars also arrive at randomly varied time intervals but must also wait until their are at least four tires before it will change the tires. Once the tires are changed, the car leaves.
Java's synchronized code segments ensure that when the car looks at the stack of tires, it sees the accurate tire count. If the tire stack did not have synchronized code segments for its accessor and mutator methods, a condition could arise where the fourth tire of a set is being placed on the stack at the same time the car is looking at the stack. In that case It would be possible that the car sees four tires when there are actually three. This could cause an error, as the tire stack would be left with -1 tires. Less than ideal.
In this simulation, there are exactly enough tires made by the factory to supply the cars. Since there are 7 cars, the factory makes 28 tires. Without synchronized code segments, there would be a chance for the simulation to run and have one or more tires remaining (having being counted incorrectly).
In this applet, however, there will never be leftover tires on the stack - your Java Virtual Machine is safe from errors. Run it a few times and see..
Click here for the source code (ZIP).
Producer/Consumer Simulation in a Java Applet
Click here to restart the simulation |
ProducerConsumer.java
/*Assignment 3
*Chad Therrien COMP354
*Producer/Consumer Simulation Applet
*Demonstrating Java's Multithreading with Synchronized Data Access
*/
import java.awt.*;
import java.awt.event.*;
import java.applet.Applet;
public class ProducerConsumer2 extends Applet implements Runnable
{
TireBin bin;
TireFactory factory;
RaceCar raceCar;
Thread mainThread;
Image carImage[] = new Image[3];
Image factoryImage[] = new Image[2];
Image tireImage[]=new Image[3];
int fState=2;
int cState=0;
int x=10, y=100;
int i;
TextArea outputSim;
static boolean runSim=false;
public void init()
{
factoryImage[0]=getImage(getCodeBase(),"factoryon.gif");
factoryImage[1]=getImage(getCodeBase(),"factoryoff.gif");
tireImage[0]=getImage(getCodeBase(),"tirebot.gif");
tireImage[1]=getImage(getCodeBase(),"tiremid.gif");
tireImage[2]=getImage(getCodeBase(),"tiretop.gif");
carImage[0] = getImage(getCodeBase(),"car1.gif");
carImage[1] = getImage(getCodeBase(),"car2.gif");
carImage[2] = getImage(getCodeBase(),"car3.gif");
outputSim=new TextArea("Simulation Stopped" +
"\n",10,70,TextArea.SCROLLBARS_VERTICAL_ONLY);
add(outputSim);
bin = new TireBin(outputSim);
factory = new TireFactory(outputSim, bin, 5);
raceCar = new RaceCar(outputSim, bin, 1);
runSim=false;
}
public void run()
//main application thread
{
runSim=false;
while(true)
{
repaint();
try
{
Thread.sleep(200);
} catch(Exception e){}
}
}
public void start()
{
mainThread = new Thread(this);
mainThread.start();
}
public void paint(Graphics g)
//update applet window based on current factory and car state variables
{
switch (fState)
{
case 0:
break;
case 1:
g.drawImage(factoryImage[0],10,240,this);
break;
case 2:
g.drawImage(factoryImage[1],10,240,this);
default:
break;
}
switch (cState)
{
case 0:
break;
case 1:
g.drawImage(carImage[0],70,390,this);
break;
case 2:
g.drawImage(carImage[1],200,390,this);
break;
case 3:
g.drawImage(carImage[2],200,390,this);
break;
case 4:
g.drawImage(carImage[0],410,390,this);
break;
default:
break;
}
if(bin.tireCount()>0)
{
g.drawImage(tireImage[0],220,345,this);
for (i = 1;i < bin.tireCount() && i <6;i++)
{
g.drawImage(tireImage[1],220,345-(i*11),this);
}
g.drawImage(tireImage[2],220,345-(i*11),this);
}
if(bin.tireCount()>6)
{
g.drawImage(tireImage[0],255,345,this);
for (i = 1;i < bin.tireCount()-6 && i <6 ;i++)
{
g.drawImage(tireImage[1],255,345-(i*11),this);
}
g.drawImage(tireImage[2],255,345-(i*11),this);
}
}
class RaceCar implements Runnable
//race car thread (the Consumer)
{
Thread thread;
private TireBin myBin;
private int number;
TextArea outputSim;
public RaceCar(TextArea outputSim, TireBin b, int number)
{
myBin = b;
this.number = number;
this.outputSim = outputSim;
thread=new Thread(this);
runSim=false;
thread.start();
}
public void run()
{
for (int i = 1;i < 8; i++)
{
cState=1;
try{Thread.sleep(200);}catch(Exception e){}
cState=2;
try{Thread.sleep(500);}catch(Exception e){}
cState=3;
try{Thread.sleep(300);}catch(Exception e){}
myBin.removeSet();
outputSim.append("Race Car got 4 tires: " + i + "\n");
cState=2;
try{Thread.sleep(300);}catch(Exception e){}
cState=4;
try{Thread.sleep(600);}catch(Exception e){}
cState=0;
try
{
Thread.sleep((int)(Math.random() * 800));
while(runSim)
{
wait();
}
} catch (Exception e) {}
}
outputSim.append("\nNo more race cars!"+ "\n\n");
outputSim.append("\tRemaining tires in tirebin: "
+ myBin.tireCount()+"\n");
if(myBin.tireCount()!=0)
{
outputSim.append("\tFault in synchronized" +
"code caused error in tire count!\n\n");
} else
{
outputSim.append("\tNo fault in synchronized" +
"code detected!\n\n");
}
}
}
public class TireBin
//the Cubbyhole
{
private int tireCount;
private int maxTires = 12;
private boolean available = false;
TextArea outputSim;
public TireBin(TextArea outputSim)
{
this.outputSim = outputSim;
tireCount=0;
}
public synchronized void removeTire()
{
boolean first = true;
while(tireCount == 0)
{
if(first)
{
outputSim.append("Race car waiting for tires" + "\n");
first = false;
}
try{wait();} catch (Exception e){}
}
tireCount--;
outputSim.append("One tire installed. Count = " + tireCount + "\n");
notifyAll();
}
public int tireCount()
{
return tireCount;
}
public synchronized void removeSet()
//Synchronization prevents any other thread from entering method
{
boolean first = true;
while(tireCount < 4)
{
if(first)
{
outputSim.append("Race car waiting for 4 tires" + "\n");
first = false;
}
try{wait();} catch (Exception e){}
}
tireCount-=4;
outputSim.append("Four tires installed! Count = " + tireCount + "\n");
notifyAll();
}
public synchronized void addTire()
//Synchronization prevents any other thread from entering method
{
boolean first = true;
while (tireCount >= maxTires)
{
if(first)
{
outputSim.append("Tire bin is FULL!" + "\n");
first = false;
fState=2;
}
try
{
wait();
}catch (Exception e) {}
}
tireCount++;
outputSim.append("Tire arrived. Count = " + tireCount + "\n");
notifyAll();
}
}
public class TireFactory implements Runnable
//Tire Factory thread (the Producer)
{
Thread thread;
private TireBin myBin;
private int number;
TextArea outputSim;
public TireFactory(TextArea outputSim, TireBin b, int number)
{
myBin = b;
this.number= number;
this.outputSim = outputSim;
thread=new Thread(this);
thread.start();
}
public void run ()
{
fState=1;
for (int i = 1; i < 29; i++)
{
try
{
Thread.sleep((int)(Math.random() * 200)+200);
while(!runSim)
wait();
} catch (Exception e) {}
myBin.addTire();
outputSim.append("Tire Factory made one tire. Made = " + i + "\n");
fState=1;
}
fState=2;
outputSim.append("Tire factory is closed"+ "\n");
}
}
}
|