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 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.
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) {
= sender;
mySender }
/**
* 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) {
= receiver;
myNextReceiver }
/**
* 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.
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);
= new EventGenerator(this, new Arrivals(), tba, tba);
myArrivalGenerator = new SResource(this, numServers, "Servers");
myServers = new RandomVariable(this, st);
mySTRV = new SingleQueueStation(this, myServers, mySTRV, "Station");
mySQS .setNextReceiver(new Dispose());
mySQS= new ResponseVariable(this, "System Time");
mySystemTime = new TimeWeighted(this, "Num in System");
myNumInSystem }
private class Arrivals implements EventGeneratorActionIfc {
@Override
public void generate(EventGenerator generator, JSLEvent event) {
.increment();
myNumInSystem.receive(new QObject(getTime()));
mySQS}
}
protected class Dispose implements ReceiveQObjectIfc {
@Override
public void receive(QObject qObj) {
// collect final statistics
.decrement();
myNumInSystem.setValue(getTime() - qObj.getCreateTime());
mySystemTime}
}
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()) {
<GetValueIfc> valueObject = customer.getValueObject();
Optionalif (valueObject.isPresent()){
= valueObject.get().getValue();
t } else {
throw new IllegalStateException("Attempted to use QObject.getValueObject() when no object was set");
}
} else {
= getServiceTime().getValue();
t }
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() {
= myWaitingQ.removeNext(); //remove the next customer
QObject customer .seize();
myResource// schedule end of service
scheduleEvent(myEndServiceAction, getServiceTime(customer), customer);
}
@Override
public void receive(QObject customer) {
.increment(); // new customer arrived
myNS.enqueue(customer); // enqueue the newly arriving customer
myWaitingQif (isResourceAvailable()) { // server available
serveNext();
}
}
class EndServiceAction implements EventActionIfc<QObject> {
@Override
public void action(JSLEvent<QObject> event) {
= event.getMessage();
QObject leavingCustomer .decrement(); // customer departed
myNS.release();
myResourceif (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.
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) {
= new Simulation("M/M/2");
Simulation sim // get the model
= sim.getModel();
Model m // add system to the main model
= new ExponentialRV(1);
ExponentialRV tba = new ExponentialRV(.8);
ExponentialRV st int ns = 2;
= new GGCQueuingStation(m, tba, st, ns);
GGCQueuingStation system // set the parameters of the experiment
.setNumberOfReplications(30);
sim.setLengthOfReplication(20000.0);
sim.setLengthOfWarmUp(5000.0);
sim= sim.makeSimulationReporter();
SimulationReporter r System.out.println("Simulation started.");
.run();
simSystem.out.println("Simulation completed.");
.printAcrossReplicationSummaryStatistics();
r}
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);
= new RandomVariable(this, new ExponentialRV(1.0/1.1));
myTBA = new RandomVariable(this, new ExponentialRV(0.8));
myST1 = new RandomVariable(this, new ExponentialRV(0.7));
myST2 = new EventGenerator(this, new Arrivals(), myTBA, myTBA);
myArrivalGenerator = new SingleQueueStation(this, myST1, "Station1");
myStation1 = new SingleQueueStation(this, myST2, "Station2");
myStation2 .setNextReceiver(myStation2);
myStation1.setNextReceiver(new Dispose());
myStation2= new ResponseVariable(this, "System Time");
mySysTime = new TimeWeighted(this, "NumInSystem");
myNumInSystem }
protected class Arrivals implements EventGeneratorActionIfc {
@Override
public void generate(EventGenerator generator, JSLEvent event) {
.increment();
myNumInSystem.receive(new QObject(getTime()));
myStation1}
}
protected class Dispose implements ReceiveQObjectIfc {
@Override
public void receive(QObject qObj) {
// collect system time
.setValue(getTime() - qObj.getCreateTime());
mySysTime.decrement();
myNumInSystem}
}