7.4 The Station Package
This section describes a JSL package that facilitates the modeling of system components that can send or receive entities. Many systems have as a basic component a location where something happens or where processing can occur. A station represents this concept. The goal of this package is to illustrate the development and use of common components that help with the modeling of simple queueing system modeling. First, an overview of the package will be presented and then the operation of important classes discussed. Then, a number of examples will illustrate how to utilize the classes.
Figure 7.3: Major Classes within Station Package
Figure 7.3 illustrates the primary classes and
interfaces within the station package. There are two key interfaces:
SendQObjectIfc and ReceiveQObjectIfc.
public interface SendQObjectIfc {
void send(QObject qObj);
}
public interface ReceiveQObjectIfc {
void receive(QObject qObj);
}The interfaces define single methods, send(QObject qObj) and receive(QObject qObj), which permit
implementors to promise the capability of receiving and sending objects
of type QObject. Figure 7.4 illustrates the connection between the Station class and SendQObjectIfc and ReceiveQObjectIfc. This design permits the development of model elements that
know how to send instances of QObject and how to receive instances of
QObject. A Station is an abstract base class that implements the
ReceiveQObjectIfc interface. Sub-classes of Station will have a
receive() method that can be called to tell the station that an instance
of a QObject should be received.
Figure 7.4: The Station Class
The following listing shows the implementation of the Station
class. The Station class may have a reference to an object that
implements the SendQObjectIfc interface and may have an object that implements
the ReceiveQObjectIfc interface. An object that implements the
SendQObjectIfc interface can be supplied to define behavior for sending
a received instance of a QObject onwards within the system. If the
SendQObjectIfc interface attribute is not supplied, then the
ReceiveQObjectIfc object is used to indicate the next location for
receipt. The protected method, send(QObject qObj),
indicates this logic. The UML diagram for the Station class is provided
in Figure 7.4. Notice that Station implements the
ReceiveQObjectIfc interface, may have a reference to a ReceiveQObjectIfc
instance, and has a reference to an instance of a SendQObjectIfc
interface.
If the client does not supplier either an object that implements the ReceiveQObjectIfc interface or an object that implements the SendQObjectIfc interface, then an exception will be thrown. The object that implements the ReceiveQObjectIfc interface is meant to be a direct connection (i.e. something that can directly receive instances of QObject). Sometimes, the logic to determine where to send the QObject is complex. In which case, the user can either sub-class the Station class or provide a instance of a class that implements the SendQObjectIfc interface. This allows the sending logic to be delegated to another class rather than forcing the user to sub-class the Station class. This flexibility can be confusing to users of the class.
public abstract class Station extends SchedulingElement implements ReceiveQObjectIfc {
/**
* Can be supplied in order to provide logic
* to send the QObject to its next receiver
*/
private SendQObjectIfc mySender;
/** Can be used to directly tell the receiver to receive the departing
* QObject
*
*/
private ReceiveQObjectIfc myNextReceiver;
/**
*
* @param parent the parent model element
*/
public Station(ModelElement parent) {
this(parent, null, null);
}
/**
*
* @param parent the parent model element
* @param name a unique name
*/
public Station(ModelElement parent, String name) {
this(parent, null, name);
}
/**
*
* @param parent the parent model element
* @param sender can be null, represents something that can send QObjects
* @param name a unique name
*/
public Station(ModelElement parent, SendQObjectIfc sender, String name) {
super(parent, name);
setSender(sender);
}
/**
* A Station may or may not have a helper object that implements the
* SendQObjectIfc interface. If this helper object is supplied it will
* be used to send the processed QObject to its next location for
* processing.
* @return the thing that will be used to send the completed QObject
*/
public final SendQObjectIfc getSender() {
return mySender;
}
/**
* A Station may or may not have a helper object that implements the
* SendQObjectIfc interface. If this helper object is supplied it will
* be used to send the processed QObject to its next location for
* processing.
* @param sender the thing that will be used to send the completed QObject
*/
public final void setSender(SendQObjectIfc sender) {
mySender = sender;
}
/**
* A Station may or may not have a helper object that implements the
* ReceiveQObjectIfc interface. If this helper object is supplied and
* the SendQObjectIfc helper is not supplied, then the object that implements
* the ReceiveQObjectIfc will be the next receiver for the QObject when using
* default send() method.
* @return the thing that should receive the completed QObject, may be null
*/
public final ReceiveQObjectIfc getNextReceiver() {
return myNextReceiver;
}
/**
* A Station may or may not have a helper object that implements the
* ReceiveQObjectIfc interface. If this helper object is supplied and
* the SendQObjectIfc helper is not supplied, then the object that implements
* the ReceiveQObjectIfc will be the next receiver for the QObject when using
* default send() method.
* @param receiver the thing that should receive the completed QObject, may be null
*/
public final void setNextReceiver(ReceiveQObjectIfc receiver) {
myNextReceiver = receiver;
}
/**
* A Station may or may not have a helper object that implements the
* SendQObjectIfc interface. If this helper object is supplied it will
* be used to send the processed QObject to its next location for
* processing.
*
* A Station may or may not have a helper object that implements the
* ReceiveQObjectIfc interface. If this helper object is supplied and
* the SendQObjectIfc helper is not supplied, then the object that implements
* the ReceiveQObjectIfc will be the next receiver for the QObject
*
* If neither helper object is supplied then a runtime exception will
* occur when trying to use the send() method
* @param qObj the completed QObject
*/
protected void send(QObject qObj) {
if (getSender() != null) {
getSender().send(qObj);
} else if (getNextReceiver() != null) {
getNextReceiver().receive(qObj);
} else {
throw new RuntimeException("No valid sender or receiver");
}
}
}7.4.1 Modeling Simple Queueing Stations
In this section, the station package is used to implement a simple
queueing station. Recall the pharmacy model. In that model, we had a Poisson arrival
process with customers arriving to a drive through pharmacy window. The
service time associated with the pharmacy was exponentially distributed
and their was one pharmacist. That situation was labeled as a M/M/1
queue. In this example, we generalize the queueing station to a G/G/c
queue. That is, any general (G) arrival process, any general (G) service
process, and (c) servers. We will model the arrival process using an
EventGenerator and model the queueing with a Queue class. In addition,
we will generalize the concept of the servers by encapsulating them in a
class called SResource (for simple resource). Finally, the Queue and the
SResource classes will be combined into one class that sub-classes
Station, called SingleQueueStation. This is illustrated in
Figure 7.5.
Figure 7.5: GGC Queueing Station Classes
In the figure, the GGCQueueingStation class uses inner classes for
handling the arrivals and for disposing customers that have completed
service. In addition, GGCQueueingStation uses an instance of
SingleQueueStation to process the customers after arrival.
Let’s first examine the organization of the GGCQueueingStation class and
how it works with the Station package. Then, we will present how the
SingleQueueStation class was implemented. As shown in
Figure 7.5
the GGCQueueingStation class uses an instance of EventGenerator. The
event generator’s action is called Arrivals, which is implemented as
an inner class of GGCQueueingStation. Secondly, GGCQueueingStation uses
an instance of the class Dispose, which implements the ReceiveQObjectIfc
interface. This instance is used as the receiver after processing by the
SingleQueueStation class. The class collects some system level
statistics.
The following code listing shows the code for the GGCQueueingStation
class. The constructor takes in instances of RandomIfc interfaces to
represent the arrival and service processes. The arrival generator and
statistical collection model elements are created within the constructor.
The third line of the constructor creates the resource that is used by the arriving customers.
The fifth line of the constructor creates an instance of a SingleQueueStation to handle the
customers. Notice that the next line defines the next receiver for the station
to be an instance of the class Dispose. When the SingleQueueStation is
done, the send() method is called (as previously described) and since a
receiver has been attached, the station uses this receiver to receive
the QObject that was completed. In this case, the QObject has statistics
collected as it leaves the system. The only other code
to note is how the EventGenerator’s action is used to send the QObject
to the SingleQueueStation. Within the Arrivals class, the receive() method of the SingleQueueStation instance is called with the created QObject. In essence, the
GGCQueueingStation class simply defines and hooks up the various
components needed to model the situation. The RecieveQObjectIfc and the
Station concept play an integral role in facilitating how the object
instances are connected.
public class GGCQueuingStation extends ModelElement {
private final EventGenerator myArrivalGenerator;
private final SingleQueueStation mySQS;
private ResponseVariable mySystemTime;
private TimeWeighted myNumInSystem;
private final SResource myServers;
private final RandomVariable mySTRV;
public GGCQueuingStation(ModelElement parent, RandomIfc tba, RandomIfc st,
int numServers) {
this(parent, tba, st, numServers, null);
}
public GGCQueuingStation(ModelElement parent, RandomIfc tba, RandomIfc st,
int numServers, String name) {
super(parent, name);
myArrivalGenerator = new EventGenerator(this, new Arrivals(), tba, tba);
myServers = new SResource(this, numServers, "Servers");
mySTRV = new RandomVariable(this, st);
mySQS = new SingleQueueStation(this, myServers, mySTRV, "Station");
mySQS.setNextReceiver(new Dispose());
mySystemTime = new ResponseVariable(this, "System Time");
myNumInSystem = new TimeWeighted(this, "Num in System");
}
private class Arrivals implements EventGeneratorActionIfc {
@Override
public void generate(EventGenerator generator, JSLEvent event) {
myNumInSystem.increment();
mySQS.receive(new QObject(getTime()));
}
}
protected class Dispose implements ReceiveQObjectIfc {
@Override
public void receive(QObject qObj) {
// collect final statistics
myNumInSystem.decrement();
mySystemTime.setValue(getTime() - qObj.getCreateTime());
}
}The last piece of this puzzle is how the SingleQueueStation class
implements the abstract base class Station and becomes a component that
can be reused in various models. Recall the logic for handling arrivals
and service completions from the pharmacy example. In that code, there was the need to have
the customer enter the queue and then check if the pharmacist was
available. If the pharmacist was available, then the customer was
removed from the queue and the end of service scheduled. In addition,
when the service is completed, if the queue is not empty, then the next
customer is started into service. The SingleQueueStation generalizes this
same logic by using an instance of SResource to represent the servers.
The following code listing illustrates the critical code for the
SingleQueueStation class. The receive() method (which must be
implemented by a sub-class of Station), shows the QObject being placed
in the queue. Then, the resource is checked to
see if it is available and then the next customer is served via the call
to serveNext(). Upon the end of service the EndServiceAction is called.
Notice that the resource is released, and then the queue is
checked. Finally, the send() method is used to send the
QObject to whereever it is intended to go.
protected double getServiceTime(QObject customer) {
double t;
if (getUseQObjectServiceTimeOption()) {
Optional<GetValueIfc> valueObject = customer.getValueObject();
if (valueObject.isPresent()){
t = valueObject.get().getValue();
} else {
throw new IllegalStateException("Attempted to use QObject.getValueObject() when no object was set");
}
} else {
t = getServiceTime().getValue();
}
return t;
}
/**
* Called to determine which waiting QObject will be served next Determines
* the next customer, seizes the resource, and schedules the end of the
* service.
*/
protected void serveNext() {
QObject customer = myWaitingQ.removeNext(); //remove the next customer
myResource.seize();
// schedule end of service
scheduleEvent(myEndServiceAction, getServiceTime(customer), customer);
}
@Override
public void receive(QObject customer) {
myNS.increment(); // new customer arrived
myWaitingQ.enqueue(customer); // enqueue the newly arriving customer
if (isResourceAvailable()) { // server available
serveNext();
}
}
class EndServiceAction implements EventActionIfc<QObject> {
@Override
public void action(JSLEvent<QObject> event) {
QObject leavingCustomer = event.getMessage();
myNS.decrement(); // customer departed
myResource.release();
if (isQueueNotEmpty()) { // queue is not empty
serveNext();
}
send(leavingCustomer);
}
}This is the same send() method that was previously discussed. Thus, because the receiver was set to be
the instance of Dispose, the departing customer will be sent to the
GGCQueueingStation where the statistics will be collected, as previously
noted.
Figure 7.6 shows the SResource class. The SResource
class models a resource that has a defined capacity, which represents
the number of units of the resource that can be in use. The capacity of
the resource represents the maximum number of units available for use.
For example, if the resource has capacity 3, it may have 2 units busy
and 1 unit idle. A resource cannot have more units busy than the
capacity. A resource is considered busy when it has 1 or more units
busy. A resource is considered idle when all available units are idle.
Units of the resource can be seized via the seize() methods and released
via the release() methods. It is an error to attempt to seize more units
than are currently available. In addition, it is an error to try to
release() more units than are currently in use (busy). Statistics on
resource utilization and number of busy units is automatically
collected.
Figure 7.6: The SResource Class
The following listing illustrates how to build and run an instance of a GGCQueuingStation by simulating a M/M/2 queue.
public static void main(String[] args) {
Simulation sim = new Simulation("M/M/2");
// get the model
Model m = sim.getModel();
// add system to the main model
ExponentialRV tba = new ExponentialRV(1);
ExponentialRV st = new ExponentialRV(.8);
int ns = 2;
GGCQueuingStation system = new GGCQueuingStation(m, tba, st, ns);
// set the parameters of the experiment
sim.setNumberOfReplications(30);
sim.setLengthOfReplication(20000.0);
sim.setLengthOfWarmUp(5000.0);
SimulationReporter r = sim.makeSimulationReporter();
System.out.println("Simulation started.");
sim.run();
System.out.println("Simulation completed.");
r.printAcrossReplicationSummaryStatistics();
}This is the same as any JSL run. In this case, the GGCQueueingStation class used a
SingleQueueStation to do, essentially, all of the work. Now, imagine
many queueing stations organized into a system. Because the
SingleQueueStation is built as a component that can be reused, it is
available for more complicated modeling.
The following code illustrates how easy it is to model a
tandem queue using the SingleQueueStation class. A tandem queue is a
sequence of two queueing stations in series, where the customers
departing the first staiton go on for further processing at the second
station. The constructor shows the creation of two
stations, where the receiver of the first station is set to the second
station (line 9). The receiver for the second station is set to an
instance of a class that implements the ReceiveQObjectIfc interface and
collects the total time in the system and the number of customers in the
system. This should begin to indicate how complex networks of queueing
stations can be formed and simulated.
public TandemQueue(ModelElement parent, String name) {
super(parent, name);
myTBA = new RandomVariable(this, new ExponentialRV(1.0/1.1));
myST1 = new RandomVariable(this, new ExponentialRV(0.8));
myST2 = new RandomVariable(this, new ExponentialRV(0.7));
myArrivalGenerator = new EventGenerator(this, new Arrivals(), myTBA, myTBA);
myStation1 = new SingleQueueStation(this, myST1, "Station1");
myStation2 = new SingleQueueStation(this, myST2, "Station2");
myStation1.setNextReceiver(myStation2);
myStation2.setNextReceiver(new Dispose());
mySysTime = new ResponseVariable(this, "System Time");
myNumInSystem = new TimeWeighted(this, "NumInSystem");
}
protected class Arrivals implements EventGeneratorActionIfc {
@Override
public void generate(EventGenerator generator, JSLEvent event) {
myNumInSystem.increment();
myStation1.receive(new QObject(getTime()));
}
}
protected class Dispose implements ReceiveQObjectIfc {
@Override
public void receive(QObject qObj) {
// collect system time
mySysTime.setValue(getTime() - qObj.getCreateTime());
myNumInSystem.decrement();
}
}