2. Inside J-Sim KernelIn this chapter, an explaination of the basic principles on which the J-Sim kernel is based will be presented. It will discuss mainly the way in which processes are switched, how they are suspended and reactivated and the relationship between a simulation and its processes. It is assumed that the reader has already some knowledge of the Java language and concurrent programming. 2.1. Possibilities Offered by JavaThe Java programming language provides a set of classes, interfaces, methods and keywords manipulating with threads. Only some of them will be mentioned, those which are particularly interesting for realization of pseudo-parallel execution. 2.1.1. The Thread ClassFirst of all, the Thread class must be mentioned. An instance of this class or its subclass (1) is able to be executed parallelly with the thread that created and ran it. Every instance of this class has its parallelly executable code stored in the run() method which is empty in the case of Thread. This method should be always overwritten in order for the thread to provide some meaningful concurrently-run services. When an instance of a Thread's subclass is created, it is passive like all ordinary Java objects. It is switched to an active state using its start() method. This causes the code of its run() method to start to be executed parallelly. Inside this method, other methods (of the same object as well of other objects) can be called without breaking up the parallelism. Once the run() method reaches its end, the object becomes passive again without the possibility to be restarted. But it can still provide non-parallel services by methods invoked in the usual way. There is always at least one thread within every Java program: the main() method of the class which was given as a parameter to the Java interpreter. Here is a little example how to create and run a thread which prints out a message: class MyThread extends Thread { public void run() { System.out.println("Hello."); } } . . . // In main() MyThread thread = new MyThread(); thread.start(); 2.1.2. Unusable Synchronization MethodsThe Thread class provides three very promising methods allowing to manipulate with a thread's execution state: suspend(), resume(), and stop(). The suspend() method temporarily suspends execution of a thread until resume() is invoked on the same thread. The last method interrupts execution of a thread and causes it to exit its run() method. However, all of them have been marked as deprecated since JDK (2) 1.2. There were very serious reasons to do so, mainly concerning security. The reasons can be found in [Lea]. 2.1.3. Usable Synchronization MethodsHowever, Java provides a very efficient way of thread synchronization, although it is not obvious for the first sight. A method (3) of a class can be marked as synchronized (using the synchronized keyword in its header) which will prevent all threads from entering method's body if there is already another thread. A lock is used to keep information whether it is possible to enter the method. If there are more protected methods within a class, they share the same lock. Note that the lock belongs to an object (instance of a class) not to a class. Here is a simple example of two synchronized methods sharing the same lock: public synchronized void a() { /* ... something is happening here ... */ } public synchronized void b() { /* ... and something else here ... */ }When a thread enters the a() method, the lock is acquired and no other thread can enter this nor the b() method. All threads attempting to do so are temporarily suspended and one of them is resumed when the first thread leaves the a() method and the lock is released. However, methods or just their parts can be synchronized by another lock than the implicit one (this). The following piece of code protects the methods' code in exactly the same way as the last one, but uses an explicit lock which must have been properly initialized. The lock can be instance of any class. public void a() { synchronized (theLock) { /* ... something is happening here ... */ } } public void b() { synchronized (theLock) { /* ... and something else here ... */ } }Note that exactly the same method of thread synchronization is used in J-Sim. In order to be able to synchronize their methods with methods of all other processes, processes must share the same lock. This will be described in the following chapters. There are techniques offered by Java that allow a thread to become temporarily suspended inside a synchronized method (or just a block of code) in order to release the lock and thus allow another thread to enter the synchronized method or block. Method wait(), introduced already in the class Object, provides this service. The suspended thread must be woken up sometimes not to spend all its life in passive state, being not able to do anything. There exist two methods usable for this purpose: notify() and notifyAll(). They don't differ in behavior if there is just one thread suspended. If there is more than one thread suspended, the notify() method wakes up just one of them, it is not specified which one. The notifyAll() method wakes up all of them. However, they cannot start to run parallelly since they wake up inside a synchronized block of code. Only one of them is allowed to run (not specified which one) and all others must wait until the selected thread leaves the synchronized block of code. As soon as it does so, another already woken-up thread is selected and allowed to run and so on. A typical example on how to use these methods (notify() is omitted here but the principle is the same) is as follows: public void sleepUntilWokenUp() { synchronized (theLock) { // This piece of code will be executed right after entering // the synchronized block. This can take a while, too, // if there is already another thread. theLock.wait(); // suspended // This piece of code will be executed // after the suspended thread // is woken up and the thread that woke him // up in wakeThemUp() // leaves the synchronized block. } } public void wakeThemUp() { synchronized (theLock) { // This piece of code will be executed right after entering // the synchronized block. This can take a while, too, // if there is already another thread. theLock.notifyAll(); // wake up all suspended threads // This piece of code will be executed right after waking other // processes up, there is no time delay. } }Classes having these synchronized methods are not usually threads but passive data containers. They use synchronization to protect their data from being overwritten or read in a bad way by two concurrently running threads trying to change and read the value of a variable in the same moment. Such classes are called monitors. However, nothing prevents programmers from implementing this mechanism into a class which is a subclass of Thread. But in this case, the programmer must be extremly careful about liveness od such threads. It is obvious that they cannot use their private locks. If a thread passivated itself, it would not be woken up anymore since other threads, trying to wake it up, would use their own private locks (myPrivateLock.notifyAll()). Therefore, the waking-up signal would never reach the suspended thread! Instead, all threads must use the same lock. Then, it is sufficient if at least one thread is in runnable state because it is able to wake up all others by invoking the notifyAll() method on their common lock. Exactly the same principle is used by J-Sim kernel to switch between user processes and the main thread, requesting to do one simulation step. 2.2. Simulation as ContainerAs it has been mentioned above, in order for user processes to switch properly they need to have one unique lock shared by all of them. Trying to analyze the situation deeper, one will find that there is not running the same number of threads as the user processes. There is always one in addition to them - the main thread, which executes the code written in method main() of the class whose name was given as the first parameter to Java interpreter java (or java.exe). This thread is created and run automatically by JVM. It is usually this thread which calls the step() method of the simulation but does not necessarily need to be. Because it is required that this main thread wait until the step is finished (and all user processes are suspended), it must be synchronized by the same lock as processes are. Actually, it is not the main thread which owns the lock. The lock is owned by the simulation and a reference to it can be obtained by invoking simulation's method getGlobalLock(). In fact, the main thread need not care about this lock at all. When it invokes the step() method of the simulation, it has full access to all its variables, therefore to the lock too. The situation is depicted in figure 2.2.. The lock named globalLock is owned exclusively by the simulation object and its processes get just a reference to it. Because every process has its simulation to which it belongs (called myParent) it can get the reference upon creation (in constructor JSimProcess) by invoking myParent.getGlobalLock(). The fact that the reference's name is ourCommonLock does not have any influence. Despite different names in different classes it is still the same object. Now, all user processes and any arbitrary thread invoking the step() method of their simulation can be synchronized by the same lock. Ignoring the rest of the image for the moment, the process switching using this lock will now be examined. 2.3. Execution of a Step - Switching Techniques2.3.1. Points of SwitchingBefore explaining the switching techniques it would be convenient to state where exactly the switching takes place. In class JSimSimulation, it is:
In class JSimProcess, switching points are:
2.3.2. Commands Used to SwitchThe deterministic switching (4) is possible thanks to the following factors:
Let's have a look now how the switching is implemented in Java. First, switching from the main thread to a process (a part of JSimSimulation.step()): runningProcessNum = calendar.getFirstProcessNum(); time = calendar.getFirstProcessTime(); /* Now sending the wake-up signal to all threads in the system */ globalLock.notifyAll(); /* Now waiting until "hold()" or "passivate()" is called. */ while (runningProcessNum != JSimCalendar.NOBODY) { try { globalLock.wait(); } catch (InterruptedException e) { /* Just ignoring this exception -- nobody should iterrupt the main thread */ } } // whileFirst, the runningProcessNum variable is set to the value which is taken from the top of simulation's calendar. This process is intended to be run in next step. Then, the time of the simulation is set to the value of the first event in the calendar. After that, all processes belonging to the simulation are woken up by a signal sent by notifyAll(). When they receive the signal, they check whether they are intended to run (they compare their number, myNumber, with the result of the call to myParent.getRunningProcessNum()). If a process finds that it is its turn, it just continues. All others suspend themselves again. After notifying all processes, the main thread passivates itself, using globalLock.wait(). Note that the notification must be done before passivation - a passivated process cannot notify any other process since it is stuck at one point. The wait() method may throw out an InterruptedException if the thread is interrupted by another thread in suspended state. Here, the case that such an exception is thrown out is simply ignored because it is not expected here. When the main thread is woken up, it checks whether it is intended to run (runningProcessNum is set to NOBODY) and continues running or passivates itself again. Because processes may only switch back to the main thread, this condition will always be false and the cycle will never run twice. However, it is a widely accepted convention to wrap every wait() into a while cycle. It is completely harmless and illustrates well the programmer's intentions. Now, a look at how processes switch back to the main thread will be given. The method of switching in hold() is identical to the method in passivate(), thus it will be explained at once: synchronized(ourCommonLock) { /* Wake up main() which is waiting now in step() */ myParent.switchToNobody(); ourCommonLock.notifyAll(); /* Going to sleep */ while (myParent.getRunningProcessNum() != getProcessNumber()) { try { setProcessState(STATE_PASSIVE); noOfCalendarEntries--; ourCommonLock.wait(); } catch (InterruptedException e) { shouldTerminate = true; requestsToTerminate++; break; } } // while setProcessState(STATE_ACTIVE); /* If we have to die this exception is propagated till run() */ if (shouldTerminate) throw new JSimProcessDeath(); } // synchronizedThis is a more complicated example of switching since it must be able to hanle process interruptions. Processes are interrupted by the simulation when the user wants to terminate the program and calls the shutdown() method. The way a process can be terminated is subject of the next chapter. First, the process tells the simulation that it wants to passivate itself and the main thread should run - myParent.switchToNobody(). Then, the waking-up signal is sent to all threads passivated with the same lock. After checking the number of process intended to run, they all passivate themselves again, except for the main thread which invoked the step() method of the simulation. Then, the currently running thread passivates itself (ourCommonLock.wait()). When it is woken up, in the next simulation step when the simulation needs to switch to the selected thread, it compares the number of the process selected to run (which has changed since this process' passivation) to its own number and decides whether to run or passivate itself again. If a process is interrupted by the mechanism mentioned above and discussed in the next chapter, it sets its interruption flag shouldTerminate to true and throws out a new instance of JSimProcessDeath exception. 2.3.3. An Example of One Simulation StepTo better illustrate what has been said about process switching, figure 2.3.3. on page 2 gives an explanation. When the main thread asks the simulation to execute a step, there are four processes already present within the simulation. They are all suspended in the passivate() or hold() methods. Some of them could be suspended in the getReady() method, too, if they have not run yet. Because J-Sim allows processes to be created dynamically when the simulation is already running, at the beginning of every step the newly added processes must be started. This task is performed by startNewProcesses(). In this example, one process was added to the simulation in the last step and must be started now. As soon as it is started, it passivates itself in the getReady() method. After new processes are started, the simulation looks into the calendar of events (using calendar.getFirstProcessNumber()) and finds that it is process number 2 which should be run in this step. It stores this number into the variable runningProcessNum and sends a wake-up signal to all suspended processes. Immediately after that, the currently running thread is passivated. When processes (including the newly created one) receive the signal and are woken up, they must decide whether to run or to passivate themselves. Most of them choose the second possibility. Only one of them, the process having number 2, decides to continue. It continues running a part of its life as the only thread within the simulation until it calls the hold() or passivate() method. When it does so, runningProcessNum is set to NOBODY by calling myParent.switchToNobody(). Then, all threads are woken up again by notifyAll(), including the main thread. Immediately after notifying them, the process having number 2 passivates itself. All other threads, except of the main one, passivate themselves again because they find that they are not intended to run. The main thread removes just executed event from the calendar and exits the step() function. One simulation step has just completed. 2.4. Life of a ProcessEvery Java thread has its own run() method that the user can (and should) overwrite in his subclasses to give his threads a specific behavior. Normally, this method does nothing and a thread which has just been started immediately terminates. Because J-Sim's class JSimProcess is a subclass of the Thread class, the run() method can be overwritten in a convenient way to fulfil the user's needs. However, this would be quite complicated for him because every J-Sim process must take care of the following things:
Since these requirements are always present and it is quite complicated to fulfil them from the user's point of view, the following strategy was applied:
2.4.1. Two Kinds of DeathTaking a deep look at the possible strategies of processes' lifes, one will find that there are several of them:
The second way of process termination - killing - requires a special mechanism which is discussed in the next chapter. 2.4.2. Killing a ProcessAs already known, the technique of process killing is used when a process is suspended in hold(), passivate() or getReady() and its simulation tries to destroy it. As mentioned in chapter 2.1.3., every suspension (a call to aLock.wait()) must be wrapped into a try block followed by an exception handler catching all instances of InterruptedException. Since it is not recommended to use deprecated methods such as stop(), the only possibility how the simulation can interrupt a process is to invoke the interrupt() method: aProcess.interrupt(). Because the simulation knows about all processes belonging to it, it can interrupt all of them. When a process is interrupted while being suspended, an instance of class InterruptedException is thrown out from the body of the wait() method and is caught in the exception handler which follows the mandatory try block. It is an easy job to set a flag saying that the process should die to true. The corresponding piece of code follows: try { setProcessState(STATE_PASSIVE); ourCommonLock.wait(); } catch (InterruptedException e) { shouldTerminate = true; }But another problem appears: The methods hold() and passivate() cannot be just exited. These methods are called from the life() method which always contains user's code. And he has no interest in taking care about exiting the life() method when it is necessary, i.e. in testing the flag of interruption. Therefore, a similar principle like in Java Core API is used: an exception is thrown out, which is not caught in the life() method but is caught in the run() method. Although this principle has been deprecated in Java Core API because it is possible to catch the exception and force the dying thread to stay alive, it suits its purpose very well. For the detailed reasons of deprecation, see [Lea], page 175. Although getReady() is not invoked from life(), it uses the same principle to unify the methods of process killing used. Here is the corresponding piece of code: if (shouldTerminate) throw new JSimProcessDeath();Since JSimProcessDeath is a subclass of RuntimeException, it need not be enumerated in the list of exceptions thrown out from a method. Therefore, the headers of methods that this concerns look as follows: private final void getReady() protected void life() protected final void passivate() throws JSimSecurityException protected final void hold(double deltaT) throws JSimSecurityExceptionNote that JSimSecurityException has nothing to do with the principle of process killing, it is just a way how to tell the user that he tries to commit an operation which is not allowed. A little summary of JSimProcessDeath spreading follows. It contains a list of all possible paths through which this exception can spread:
2.4.3. Life of a Process IllustratedThe run() method, complexly described in previous chapters, is depicted in figure 2.4.3. on page 2. The thick black arrow leading from getReady() to life() represents the moment when the process is selected for the first time by the simulation to run. The second thick black line leading from life() to finish() represents the process' natural death, i.e. the moment when the process reaches the end of life() by its own decision. As it can be seen, there are three points where the process can be killed by its simulation: in getReady(), hold() and passivate(). It does not matter that the hold() and passivate() methods are not invoked directly from run() because the JSimProcessDeath exception is able to propagate through the life() method since the user does not care about it. The exception is caught in the handler, placed between the life() and finish() methods. Then, the execution continues by invoking finish(), as usual. 2.4.4. Possible Danger of Catching JSimProcessDeathThe solution used by J-Sim to kill processes contains one hidden danger. Nothing can prevent the user from catching all JSimProcessDeath exceptions in the body of his life() method. He can do that if he uses at least one hold() or passivate() method in his life() because this exception is thrown out by them. In that case, the user is fully responsible for terminating the process and J-Sim has no other possibilities to do it. The following list contains possible solutions how to treat JSimProcessDeath exception in the life() method, beginning with the best one and ending with the worst one:
2.4.5. Prevention from CollapsesDuring execution of the process' life, number of runtime exceptions (5) can be thrown, for example NumberFormatException if the user tries to convert a badly formatted string to number. These exceptions would normally propagate from life() through run() to JVM which would destroy the affected thread. Because this thread is the only running one at the moment, its destruction would inevitably lead to deadlock. All other threads, including the main thread, would stay suspended and the only visible result would be a "hang-up". To avoid this situation, another exception handler must be inserted into the run() method, which will catch all exceptions caused by the user's programming mistakes. After catching the exception, the process continues as a normally terminated process (see natural death in chapter 2.4.1.), i.e. by calling the finish() method. 2.5. A Deep View of JSimSimulationIn this chapter, a presentation of the JSimSimulation class in deeper detail with focus on some important fields and methods it provides for its processes and for the user will be given. Not all of them will be mentioned, only those of sufficient importance. JSimSimulation is one of J-Sim's fundamental classes. It is inherited from Object and thus has no special pre-programmed behavior or services to offer. Only this class is able and allowed to execute the simulation in the step-by-step manner mentioned in chapter 1.1.. An instance of this class must be present in every J-Sim simulation program. If it is not, it is not possible to create any process and queue (see chapters 2.6. and 3.). It is not necessary to create a new subclass of JSimSimulation. It is a ready-to-use class whose instances are fully functional. Here is a list of principal tasks that every simulation object is given:
2.5.1. Information about ProcessesEvery instance of JSimSimulation is equipped with a queue of JSimProcessInfo elements where it stores information about processes present in the simulation. The name of the queue is infoQueue. Whenever a new process is added to the simulation, a new JSimProcessInfo object is created and inserted to the queue. JSimSimulation class provides a method called addProcess(), taking one parameter - a reference to the process to be added. This method is called implicitly from the constructor of JSimProcess, therefore the user need not take care about that. There are two methods deleting processes from the queue: deleteProcess() and deleteAllProcesses(). The difference between them is that deleteProcess() deletes just one process from the queue, whose number is specified as parameter of the method, while deleteAllProcesses() destroys the whole queue, sending all processes in the queue an interrupt signal. Why is the signal sent in deleteAllProcesses() but not in deleteProcess()? The answer is easy: deleteProcess() is called by the process itself from its finish() method when it is dying by a natural death while deleteAllProcesses() is called from simulation's shutdown() method when there are some threads suspended. It is thus necessary to kill them by interrupting their suspension. What follows after the interrupt signal is received is described in chapter 2.4.2.. 2.5.2. The CalendarThe calendar (a variable called calendar) is an instance of the JSimCalendar class. It is nothing more than a queue of JSimCalendarItem elements. Every such element holds information about one event: time that the event is scheduled for - time - and number of the process which will be run at that time - processNum. The calendar provides some important methods that the simulation uses:
Since processes do not know anything about the existence of the calendar they cannot use the methods mentioned above. Instead, JSimSimulation offers them methods called addEntryToCalendar() and deleteEntriesInCalendar() to insert and delete events. These functions are marked as protected and therefore can be invoked only from JSimProcess, not from its subclasses not being in the package cz.zcu.fav.kiv.jsim. They are used in activate(), cancel(), and hold() when processes need to create or delete an event. 2.5.3. The Simulation TimeThe simulation time, stored in variable time, is a floating-point number which changes just before a simulation step starts to be executed. As it can be seen in figure 1.2. on page 1, the value of the change is not constant and cannot be predicted in any way unless the time of the next event in the calendar is known. There is only one place in the code of JSimSimulation where the simulation time is changed: it is in the step() method when the new time is taken from the calendar and before the main thread switches to a selected process: time = calendar.getFirstProcessTime();Although it cannot be set by anybody else than the simulation, it can be read by everybody: processes, user - outside the step() function, etc. Processes can obtain the simulation time by calling myParent.getCurrentSimulationTime(), the user can obtain it by calling x.getCurrentsimulationTime() where x is a simulation object created in the main program. 2.5.4. Switching to Graphics Mode and BackEvery J-Sim simulation is able to run in two modes: in console mode and graphics mode. Implicitly, it is the console mode that the simulation runs in and the user must use a method called runGUI() to switch to graphics mode. Because it is not as trivial of an operation as it seems to be at the first sight, it must be explained here. As stated in chapter 2.2., every simulation is able to create its own simulation window which is an instance of the JSimGUIMainWindow class. The internals will be discussed in chapter 2.7., while here the explaination of the relationship between the simulation and its window will be given. To create a new window (an instance of JSimGUIMainWindow) and open it using its show() method, one will find that the window is visible and properly functionning but the show() method does not suspend the calling thread for the time the window is open. Instead, four (7) new threads are created to handle the user input (mouse, keyboard) and perform graphical operations. The calling thread continues running without waiting for the window's closure. This is the fundamental problem that must be dealt with. The solution is obvious if figure 2.5.4. on page 2 is examined. It is exactly the same figure that was presented on page 2. If the main thread after window's creation is suspended until the time the window is closed, the desired behavior will be obtained. To be able to do that, a new lock must be introduced, shared by the simulation object and the window. This lock is called graphicLock both in JSimSimulation and in JSimGUIMainWindow. Again, the lock is physically created only once (in the simulation's constructor) and the simulation window has only a reference to it. The mechanism of switching is less complicated than the mechanism of process switching but it will be explained anyway. When the main thread creates the window and opens it, it must passivate itself until the main window sends it a wake-up signal. Again, the wait() method of the shared lock is used: synchronized (graphicLock) { mainWindow = new JSimGUIMainWindow(this); while (windowOpen) { try { graphicLock.wait(); } catch (InterruptedException e) { windowOpen = false; // We must get out } } // while } // synchronizedWhen the main thread is woken up, it checks the value of windowOpen. If it is false, it exits the while cycle and consequently exits the runGUI() method. At this point, windowOpen should never be true again because the only sender of the wake-up signal is the main window which always sets this variable to false before the signal is sent. The following is the corresponding piece of code in JSimGUIMainWindow's event handler: synchronized (graphicLock) { myParent.windowIsClosing(); graphicLock.notifyAll(); hide(); } // synchronizedSince the window itself cannot set the value windowOpen to false because it is a private variable, it must use the windowIsClosing() method of the simulation - myParent. Then, it sends the wake-up signal to all threads suspended at the lock. However, there is only one such a thread - the main one. After that, the window is hidden. Once a graphics window using AWT (8) services is created, Java creates some auxiliary threads, as mentioned above. These threads are not destroyed when the window is closed, they cannot be destroyed by sending them the interrupt signal - aThread.interrupt() either, because this signal can be (and probably is) ignored by them. Since the Thread.destroy() method has not been implemented (9) there is no other way to prevent the program from hanging up unless the System.exit() method is used to exit back to operating system. Because it is done in the shutdown() method which is always called at the end of the program, neither the simulation nor the user are affected. 2.5.5. The step() Method in DetailFor the user, the step() method is the only really useful simulation's method. Although the principle of process switching, managed by this method, has been already discussed in chapter 2.3.2., a complex and detailed look at the method, explaining all possibilities that can occur during its execution, will be given. First of all, the simulation must start all processes which were added to the simulation during last step. Therefore, startNewProcesses() is invoked at the very beginning of the method. After that, the execution differs according to the state in which the simulation is. The state is stored in a variable called simulationState. Every simulation can be in one of the three following states:
If the simulation has not been started yet, the step() method checks whether there is at least one process within the simulation. If there is not, the simulation sets the state to terminated and does not continue anymore, otherwise it sets the state to running (in progress) and continues normally. If the simulation has already terminated, the step() method does not do anything and simply exits. In the usual case (simulationState is equal to SIMULATION_IN_PROGRESS), the step() method takes from the calendar the first process' number and time and store these values to runningProcessNum and time. Then it checks whether the selected process really exists (it must be found in the queue of processes inserted into the simulation - infoQueue) and has not finished yet - isAlive() must return true. When one of the tests fails, it is necessary to repeat the selection. If there are no more events in the calendar or infoQueue is null (there are no processes), the simulation is terminated. Finally, when a process is selected, the switching mechanism, described in chapter 2.3.2., is used, the selected process is woken up and running and the main thread is suspended. When the process switches back to the main thread, the event just executed is removed from the calendar by calling calendar.jump(). The method returns a boolean value saying whether it is possible to execute another step or not. 2.6. A Deep View of JSimProcessLike JSimSimulation, JSimProcess is one of J-Sim's fundamental classes. It is a subclass of the Thread class and therefore inherits all its possibilities, mainly the possibility to be executed concurrently to other threads. New subclasses of JSimProcess should be always created since the life() method, representing the life of the process, is initially empty in JSimProcess. The only necessary changes in such a subclass are:
2.6.1. States of a ProcessDuring its life, every process can be in one of four different states:
Unlike in C-Sim, there is not any state called scheduled. The reasons for this will be discussed in chapter 2.6.2.. 2.6.2. The Concept of Over-schedulingIn C-Sim, every process can have at most one event in the calendar. In J-Sim, a different approach was chosen. A process can have unlimited number of events in the calendar. This implies the possibility to schedule an already scheduled process. All already present events belonging to the process are kept in the calendar and a new one is inserted there. There are three possibilities how a new event can be added to the calendar:
Because these methods can be combined, there can be more than one event in the calendar, belonging to a process. It is guaranteed that the process will be woken up at all desired times. However, it is not guaranteed that the process will be passivated for the exact time given as parameter to the hold() method. Another process can activate the process (before or after the process calls hold()) at a time lying in the "passive" interval and the process must be woken up at that time and therefore, must exit the hold() method. In fact, this allows us to "clone" process' life because this method doubles the number of process' events in the calendar. Therefore, over-scheduling can be considered as extremely useful when the user needs to add process scheduling times from outside but also very dangerous if the user depends on the length of time gaps between two successive steps of process' life. Since there can be any non-negative number of events belonging to a process, there is no need to register whether the process is scheduled or not. In fact, a process can be scheduled (or not scheduled) in any of the four previous states mentioned in chapter 2.6.1.:
2.6.3. Five Principal MethodsThere are five principal methods provided by the JSimProcess class for the user: life(), activate(), cancel(), hold() and passivate(). The first one is intended to be overwritten in user's subclasses, the four others are marked as final and therefore are unchangeable. The life() method represents life of a process. The user can put any possible code in it, also code creating new object, including new processes. Initially, this method is empty and it has no sense not to overwrite it in user's subclasses. This method is called automatically from run() and should never be called explicitely by user. To avoid this, the method is marked as protected and thus can be called only from methods of the same class (JSimProcess) or of the same package (cz.zcu.fav.kiv.jsim). The life of a process can be divided into several parts, using methods hold() and passivate(). The method's header is: protected void life()The activate() method inserts a new event into simulation's calendar. The event belongs to the process whose method is invoked, not to the process which invokes it during its life. The method takes one parameter: absolute time of activation. The method is marked as public and thus can be called by any process or even the main thread. The method's header is: public final void activate(double when)The cancel() method deletes all process' events from the calendar. If the process is passive, it will not be woken up anymore unless activated again by another process. A process can invoke this method on itself as well as on any other process - the method is marked as public. The method's header is: public final void cancel()The hold() method temporarily suspends the calling process and wakes it up after the time specified as method's parameter. In fact, it adds a new event to the calendar and then passivates the process. A process can invoke this method on itself only, not on another process. If it tries to do so, a JSimSecurityException is thrown out. The method's header is: protected final void hold(double deltaT) throws JSimSecurityExceptionThe passivate() method suspends the calling process without creating a new event for it. If the process is not over-scheduled now or woken up later by another thread, it will stay passive forever. A process can invoke this method on itself only, not on another process. If it tries to do so, a JSimSecurityException is thrown out. The method's header is: protected final void passivate() throws JSimSecurityException 2.7. A Deep View of JSimGUIMainWindowThe JSimGUIMainWindow class provides a user-friendly method how to run a simulation. Instead of executing steps in the way prescribed in the main program, the user can interactively decide how to proceed with the simulation. The window provides the following possibilities:
In order to handle the user's input (mouse and keyboard input) properly, it creates and registers two event listeners - instances of JSimGUIMainWindowWindowAdapter (for events generated by the window's frame, such as the window's closure) and JSimGUIMainWindowActionAdapter (for events generated inside the window, such as button presses). To describe the model of event creation, propagation and handling, used in AWT (10) is beyond possibilities of this paper. A lot of information on this subject can be found for example at [Java]. The event listeners are the window's inner classes (11) and thus have unlimited access to all of the window's fields and methods. In fact, they do not do anything else except transformation of events, sent by AWT, to method invoking. Here is a list of the events and their corresponding methods, with a brief description:
The main window has a text area covering most its surface. When the simulation runs in graphics mode, the output generated by JSimProcess.message() and JSimProcess.error() methods is redirected to this area. In the console mode, these methods print to System.out and System.err, respectively.
|