【javasim】示例程序解读

 JavaSim是面向对象的Java离散事件仿真工具包,它是一个原始的C++模拟仿真工具包的Java实现。更多内容请移步至:【javasim】一个java下的建模仿真平台,如果需要,项目文件也可以直接从这里下载。

本文将会研究关于JavaSim的一个仿真实例,即工厂作业加工仿真。首先看一下需要的类。

注意到:在MachineShop、Breaks、Arrivals和Machine中都提供了一个关于run方法的实现。因为它们都继承自SimulationProcess类, 因此,需要提供run方法完成具体的任务。

1. Main

先看一下Main方法的具体实现。

public static void main (String[] args)
    {
        boolean isBreaks = false;

        for (int i = 0; i < args.length; i++)
        {
            if (args[i].equalsIgnoreCase("-help"))
            {
                System.out.println("Usage: Main [-breaks] [-help]");
                System.exit(0);
            }
            if (args[i].equalsIgnoreCase("-breaks"))
                isBreaks = true;
        }

        MachineShop m = new MachineShop(isBreaks);

        m.await();

        System.exit(0);
    }

可以看到,Main方法的实现相当简单,先是接受命令行的参数,判断是否breaks,如果true,意味着考虑机器故障的情况,否则,不考虑机器故障的情况。接着,创建了一个MachineShop对象,同时将是否breaks传入。再然后,调用MachineShop的await方法。

2. MachineShop

先看一下MachineShop的await方法的具体实现。

public void await ()
    {
        this.resumeProcess();//恢复线程运行,第一次调用,防止线程阻塞
        SimulationProcess.mainSuspend();//阻塞主进程
    }

 根据之前的介绍,SimulationProcess类继承了Thread类,resumeProcess的主要作用是恢复线程的运行。SimulationProcess.mainSuspend方法的主要作用是阻塞主进程,使主进程进入等待状态。

2.1 SimulationProcess对象的resumeProcess方法

SimulationProcess对象的resumeProcess方法恢复一个特定的线程运行。如下所示,该方法通过一个信号量mutex,控制线程同步,通过notify方法,唤醒一个阻塞的SimulationProcess线程运行。其中,count为正时,代表当前可用的资源个数;否则,其绝对值表示阻塞的进程的个数。

/**
     * Resume the specified process. This can only be called on a process which
     * has previously been Suspend-ed or has just been created, i.e., the
     * currently active process will never have Resume called on it.
     */

    protected void resumeProcess ()
    {
        /*
         * To compensate for the initial call to Resume by the application.
         */

        if (SimulationProcess.Current == null)
        {
            SimulationProcess.Current = this;
            wakeuptime = SimulationProcess.currentTime();
        }

        if (!terminated)
        {
            if (!started)
            {
                started = true;
                start();//开始执行当前线程,Java虚拟机将会调用该线程类的run方法
            }
            else
            {
                synchronized (mutex)
                {
                    count++;

                    if (count >= 0)
                        mutex.notify();//唤醒一个等待的线程,调用wait方法的线程
                }
            }
        }
    }

注意到,在调用await方法之前,没有使用Simulation.Start方法启动模拟,所以,此时(第一次调用)会执行start()方法,开辟新的线程, 同时执行当前对象的run方法。

2.2 MachineShop的run方法的具体实现

public void run ()
    {
        try
        {
            Breaks B = null;
            Arrivals A = new Arrivals(8);//作业的到达服从均值为8的指数分布
            MachineShop.M = new Machine(8);
            Job J = new Job();//创建作业,并将作业加入到工厂的加工队列中,同时判断队列是否为空,机器是否空闲,机器是否正常,若是,则将工厂任务添加到调度器Scheduler

            A.activate();//向队列中加入当前仿真对象Arrival

            if (useBreaks)//判断是否考虑机器故障
            {
                B = new Breaks();
                B.activate();
            }

            Simulation.start();//仿真开始

            while (MachineShop.ProcessedJobs < 1000)//加工不少于1000件产品
                hold(1000);


            //输出结果
            System.out.println("Current time "+currentTime());
            System.out.println("Total number of jobs present " + TotalJobs);
            System.out.println("Total number of jobs processed "
                    + ProcessedJobs);
            System.out.println("Total response time of " + TotalResponseTime);
            System.out.println("Average response time = "
                    + (TotalResponseTime / ProcessedJobs));
            System.out
                    .println("Probability that machine is working = "
                            + ((MachineActiveTime - MachineFailedTime) / currentTime()));
            System.out.println("Probability that machine has failed = "
                    + (MachineFailedTime / MachineActiveTime));
            System.out.println("Average number of jobs present = "
                    + (JobsInQueue / CheckFreq));

            Simulation.stop();

            A.terminate();
            MachineShop.M.terminate();//终止

            if (useBreaks)
                B.terminate();

            SimulationProcess.mainResume();//恢复主进程
        }
        catch (SimulationException e)
        {
        }
        catch (RestartException e)
        {
        }
    }

可以看出,上述代码的主要工作是创建了Arrival、Breaks、Machine和Job对象,同时,激活Arrival对象和Breaks对象,即将Arrival对象和Breaks对象加入到调度队列Scheduler Queue中。然后,通过Simulation.Start方法启动仿真过程。接着是循环地检查已经处理的作业数量是否超过1000。若是,则将结果打印出来。最后,停止模拟,调用Arrival、Breaks和Machine的terminate方法结束各个子进程的运行。

2.3 SimulationProcess 对象的 hold 方法

SimulationProcess 对象的 hold 方法将会调用activateDelay方法将当前进程放入到调度队列Scheduler Queue中,并且在t时刻之后激活,即阻塞当前线程一段时间。这里,需要注意的是,位于调度队列Scheduler Queue中的任务是按照计划开始时间递增的顺序排列,即队头是计划开始时间最早的调度。这里,hold方法可以简单地理解为线程切换与线程阻塞

/**
     * Hold the current process for the specified amount of simulation time.
     */

    protected void hold (double t) throws SimulationException, RestartException
    {
        if ((this == SimulationProcess.Current)
                || (SimulationProcess.Current == null))
        {
            wakeuptime = SimulationProcess.NEVER;
            activateDelay(t, false);
            suspendProcess();
        }
        else
            throw new SimulationException("Hold applied to inactive object.");
    }

2.4 SimulationProcess 对象的 activateDelay 方法

SimulationProcess对象的activateDelay的详细实现如下。可以看出,其主要工作是将当前任务插入到调度队列Scheduler Queue中,将当前进程的计划唤醒时间设置为延迟时间加上当前模拟时钟时间,即完成切换和阻塞前的准备工作。

/**
     * This process will be activated after 'Delay' units of simulation time.
     * This process must not be running, or on the scheduler queue. 'Delay' must
     * be greater than, or equal to, zero. If 'prior' is true then this process
     * will appear in the simulation queue before any other process with the
     * same simulation time.
     * 
     * @param Delay the time by which to delay this process.
     * @param prior indicates whether or not to schedule this process occurs before any other process with the same time.
     * @throws SimulationException thrown if there's an error.
     * @throws RestartException thrown if the simulation is restarted.
     */

    public void activateDelay (double Delay, boolean prior)
            throws SimulationException, RestartException
    {
        if (terminated || !idle())
            return;

        if (!checkTime(Delay))
            throw new SimulationException("Invalid delay time " + Delay);

        passivated = false;
        wakeuptime = Scheduler.getSimulationTime() + Delay;
        Scheduler.getQueue().insert(this, prior);
    }

2.5 SimulationProcess 对象的 suspendProcess 方法

SimulationProcess对象的suspendProcess方法所做的任务是首先进行调度,即从Scheduler Queue中取下一个任务。接着,判断调度是否成功。若调度成功,则进行等待;若调度失败,则不进行任何操作。其主要功能是实现线程的切换与阻塞。

/**
     * Suspend the process. If it is not running, then this routine should not
     * be called.
     */

    protected void suspendProcess () throws RestartException
    {
        try
        {
            if (Scheduler.schedule())
            {
                synchronized (mutex)
                {
                    count--;

                    if (count == 0)
                    {
                        try
                        {
                            mutex.wait();//阻塞进程
                        }
                        catch (Exception e)
                        {
                        }
                    }

                }
            }
        }
        catch (SimulationException e)
        {
        }

        if (Simulation.isReset())
            throw new RestartException();
    }

 其中,Scheduler 的 schedule 方法实现如下所示。 首先,取调度队列Scheduler Queue的队头元素,接着,运行其resumeProcess方法,恢复一个特定的线程运行。返回true,代表调度成功。在进行调度之前首先调用Simulation.isStarted方法检查模拟是否开始。若没有开始,将会抛出异常。如下所示,schedule方法将会把当前运行的任务保存到Simulation.Current静态变量中。这里,可以将schedule方法理解为线程切换

/**
     * It is possible that the currently active process may remove itself
     * from the simulation queue. In which case we don't want to suspend the
     * process since it needs to continue to run. The return value indicates
     * whether or not to call suspend on the currently active process.
     */
    
    static synchronized boolean schedule () throws SimulationException
    {
	if (Simulation.isStarted())
	{
	    SimulationProcess p = SimulationProcess.current();
	    
	    try
	    {
		SimulationProcess.Current = Scheduler.ReadyQueue.remove();//当前运行的任务在SimulationProcess的静态变量Current中保存
	    }
	    catch (NoSuchElementException e)
	    {
		System.out.println("Simulation queue empty.");
	    }

	    if (SimulationProcess.Current.evtime() < 0)
		throw new SimulationException("Invalid SimulationProcess wakeup time.");//判断计划开始时间是否合理
	    else
		Scheduler.SimulatedTime = SimulationProcess.Current.evtime();//设置模拟的仿真时间为计划开始时间

	    if (p != SimulationProcess.Current)
	    {
		SimulationProcess.Current.resumeProcess();//恢复运行线程
		
		return true;
	    }
	    else
		return false;
	}
	else
	    throw new SimulationException("Simulation not started.");
    }

2.6 线程切换与阻塞

 线程阻塞:获取调度队列Scheduler Queue,并向其插入当前的线程。下面是SimulationProcess类中的activateDelay方法的内容展示。

线程切换:从调度队列Scheduler Queue中删除队头元素,并放到SimulationProcess.Current静态属性中。下面是Scheduler类中的schedule方法的内容展示。 

 

 线程阻塞后,当前线程被插入到调度队列Scheduler Queue中。

3. Machine

下面看一下Machine的run方法的具体实现。while循环判断当前进程是否结束。若结束,则不再执行while循环,并退出run方法,Machine进程结束。首先,设置忙碌标志位为true。接着,循环判断作业队列Job Queue是否为空。若队列非空,计算作业批次和作业总数量,随机等待指数分布的时间,模拟作业的加工。将当前的作业加入到调度队列Scheduler Queue中。接着,计算开机时间,调用Job的finished方法,计算响应时间。设置忙碌标志位为空闲。最后,取消当前任务。

public void run ()
    {
        double ActiveStart, ActiveEnd;

        while (!terminated())
        {
            working = true;//忙碌标志位

            while (!MachineShop.JobQ.isEmpty())
            {
                ActiveStart = currentTime();
                MachineShop.CheckFreq++;//作业批次

                MachineShop.JobsInQueue += MachineShop.JobQ.queueSize();
                J = MachineShop.JobQ.dequeue();

                try
                {
                    hold(serviceTime());//随机加工指数分布的时间
                }
                catch (SimulationException e)
                {
                }
                catch (RestartException e)
                {
                }

                ActiveEnd = currentTime();
                MachineShop.MachineActiveTime += ActiveEnd - ActiveStart;//计算开机时间
                MachineShop.ProcessedJobs++;//处理的作业个数加1

                /*
                 * Introduce this new method because we usually rely upon the
                 * destructor of the object to do the work in C++.
                 */

                J.finished();//作业处理完成,计算响应时间
            }

            working = false;

            try
            {
                cancel();
            }
            catch (RestartException e)
            {
            }
        }
    }

其中,SimulationProcess 对象的 cancel 方法取消任务的实现具体如下所示。若当前任务在调度队列Scheduler Queue中,则将其从调度队列中删除,否则,将其直接阻塞,即设置当前对象的wakeuptime为SimulationProcess.NEVER。

/**
     * Cancels next burst of activity, process becomes idle.
     * 
     * @throws RestartException thrown if the simulation is restarted.
     */

    public void cancel () throws RestartException
    {
        /*
         * We must suspend this process either by removing it from the scheduler
         * queue (if it is already suspended) or by calling suspend directly.
         */

        if (!idle()) // process is running or on queue to be run
        {
            // currently active, so simply suspend

            if (this == SimulationProcess.Current)
            {
                wakeuptime = SimulationProcess.NEVER;
                passivated = true;
                suspendProcess();
            }
            else
            {
                Scheduler.unschedule(this); // remove from queue
            }
        }
    }

根据cancel方法的调用方式不同,可以分为:主动调用被动调用。如果是当前运行的线程是Machine对象自身,那么就称其为主动调用,否则,称为被动调用。

调用方式 说明
主动调用 使得当前线程失活,并且唤醒时间设置为SimulationProcess.NEVER,进入被动passive状态,同时阻塞当前线程()
被动调用 将当前进程(非Machine对象)从调度队列Scheduler Queue中删除,同时使其失活,进入被动passive状态

线程一旦进入被动模式,除非另一个进程将它带回到队列中,否则它将不会执行任何进一步的操作。

另外,unschedule() 方法和失活方法deactivate() 如下所示。

static synchronized void unschedule (SimulationProcess p)
    {
	try
	{
	    Scheduler.ReadyQueue.remove(p); // remove from queue
	}
	catch (NoSuchElementException e)
	{
	}

	p.deactivate();
    }
void deactivate ()
    {
        passivated = true;
        wakeuptime = SimulationProcess.NEVER;
    }

4. Arrivals

Arrivals对象也继承自SimulationProcess类,为了模拟作业的随机产生,采用一个均值为8的指数分布的随机数生成器ExponentialStream对象。每次调用hold方法,将当前对象插入到调度队列Scheduler Queue中,并随机等待一段时间。最后,创建一个作业。

public void run ()
    {
        while (!terminated())
        {
            //输出当前线程的信息
            System.out.println(currentTime() + " " + Thread.currentThread().getName() + ": Arrival");
            
            try
            {
                hold(InterArrivalTime.getNumber());
            }
            catch (SimulationException e)
            {
            }
            catch (RestartException e)
            {
            }
            catch (IOException e)
            {
            }

            new Job();
        }
    }

5.Job

Job的构造函数如下所示。其中,将作业的到达时间记录下来,判断作业队列是否为空,若作业队列为空,则代表机器对象已经执行结束或者机器中途发生了故障,需要重新将Machine对象加入到调度队列Scheduler Queue中。接着,将当前作业加入到作业队列中,同时,作业计数加1。

public Job()
    {
        boolean empty = false;

        ResponseTime = 0.0;
        ArrivalTime = Scheduler.currentTime();//作业到达时间

        empty = MachineShop.JobQ.isEmpty();
        MachineShop.JobQ.enqueue(this);//将当前作业加入到队列中
        MachineShop.TotalJobs++;//总作业加1

        if (empty && !MachineShop.M.processing()
                && MachineShop.M.isOperational())//若队列为空,同时机器空闲,同时机器正常
        {
            try
            {
                MachineShop.M.activate();//将工厂任务添加到调度器Scheduler
            }
            catch (SimulationException e)
            {
            }
            catch (RestartException e)
            {
            }
        }
    }

6.Breaks

Breaks对象的run方法如下所示。为了便于调试,笔者输出了当前线程信息。首先,为了模拟机器坏掉,先将当前的线程放入到调度队列Scheduler Queue中,并阻塞一段时间。接着,通知机器坏掉了,并且将其从调度队列Scheduler Queue中删除。然后,判断当前作业队列中是否还有作业,若有,则设置标志interrupt_service。为了模拟机器维修过程,又将当前线程放入到调度队列Scheduler Queue中,并阻塞一段时间。最后,通知维修完成。若interrupt_service为true,则将剩余作业处理完成后,激活线程,否则,直接激活线程。

public void run ()
    {
        while (!terminated())
        {
            //输出当前线程的信息
            System.out.println(currentTime() + " " + Thread.currentThread().getName() + ": Breaks");
            
            try
            {
                double failedTime = RepairTime.getNumber();

                hold(OperativeTime.getNumber());

                MachineShop.M.broken();//通知机器坏掉了
                MachineShop.M.cancel();//将其从队列中删除

                if (!MachineShop.JobQ.isEmpty())//若当前作业队列中含有其他的作业,则将设置标志,说明正在维修
                    interrupted_service = true;

                hold(failedTime);

                MachineShop.MachineFailedTime += failedTime;
                MachineShop.M.fixed();//维修完成

                if (interrupted_service)
                    MachineShop.M.activateAt(MachineShop.M.serviceTime()
                            + currentTime());//中断服务,代表还有作业需要处理,就先处理机器坏掉之前的作业,再激活
                else
                    MachineShop.M.activate();

                interrupted_service = false;
            }
            catch (SimulationException e)
            {
            }
            catch (RestartException e)
            {
            }
            catch (IOException e)
            {
            }
        }
    }

注意到上述实现中包含下述的代码,用于模拟机器突然发生故障。此种情况为被动调用cancel方法,因此,将会直接将其从调度队列Scheduler Queue中删除。

MachineShop.M.cancel();//将其从队列中删除