OO第二单元总结(多线程电梯)

oo第二单元作业的作业围绕电梯调度的问题进行,从单部电梯傻瓜调度、单部电梯捎带式调度、多部电梯多线程逐渐深入。下面是第二单元作业的总结。

一、单部多线程傻瓜调度(FAFS)电梯的模拟

1、作业内容和思路

第一次作业帮助我们入门多线程编程,熟悉多线程的使用。设计上也非常简单,主要是通过主线程调用输入模块读入PersonRequest,并将其保存到电梯队列里,电梯线程在队列不空的时候不断的处理其中的PersonRequest,为空时等待主线程读入,wait和notify的使用可以参考ppt中的生产者消费者的例子,关于判断停止,在主线程收到关闭信号后,电梯线程处理完当前队列里的请求后就退出线程。对于每个PersonRequest的处理也非常的傻瓜式,先移动到From层,然后开门,人进入,关门,移动到To层,然后开门,人出去关门这样一个流程处理,在适当的位置插入sleep函数。

2、UML设计

简单来说有两个类,一个是controller类,一个是elevator类,本次作业中,因为任务比较简单,controller没有成为一个线程,事实上是通过主线程作为一个控制器的,采用单例模式,代码示例如下

 public static Controller getController() {
        if (con == null) {
            synchronized (Controller.class) {
                if (con == null) {
                    con = new Controller();
                }
            }
        }
        return con;
    }

elevator类中实现了新加入一个请求和处理请求两个主要功能。UML图如下

3、度量分析

由于本次作业任务和设计都很简单,因此复杂度被控制在比较小的数值上。

 

代码行数上也不是很多,工作量不是很大。

 

4、总结

第一次作业总的来说比较简单,重点在于熟悉多线程编程的套路,由于要求比较低,设计的比较简单,以后的作业中还需要改进这种设计,比如将控制器也作为线程等等更具有灵活性。

二、单部多线程可捎带调度(ALS)电梯的模拟

1、作业内容和思路

本次作业依然要实现单电梯的调度,不同的地方在于这次在性能上要求能实现可稍带乘客,因此首先,电梯处理乘客请求时不能像上次一样针对每个请求依次处理,二是要在每一层分析能否捎带,捎带策略上我采取的是与指导书上相似但略有不同的“LOOK”算法,所谓LOOK算法类似于扫描算法,扫描算法中,电梯不断的从最底层到最高层扫描,遇到乘客则开门让其进入,乘客到站则开门让其出去,而LOOK算法改进在,最底层和最高层不是电梯所能达到的极限楼层而是根据当时等待的乘客和电梯中的乘客的楼层需求指定,跑到当前所需的最高楼层和最低楼层后转变方向,本质上来说也可以实现捎带。设计上,在电梯类中有两个数组来记录当前等待的乘客和已经在电梯里的乘客,处理上,到达某个楼层后判断是否要开门,是否要调转方向,是否运送完所有乘客进入待机模式。停机问题和上次作业类似,都是当输入模块关闭后,电梯把目前队列中的所有乘客送达后线程停止。

2、UML设计

类的设计上和上次作业基本相同,依然采用控制器和电梯两个类,控制器模块基本没有更改,而电梯类中的处理函数被细化为一个while循环,停止条件是所有队列中的乘客接送完毕,while中依次判断是否需要开关门,是否待机,是否需要更改运行方向,向运行方向移动一层。代码示例如下

    private void process() {
        while (true) {
            if (ifInOut()) {
                open();
                getin();
                getout();
                close();
            }
            if (ifRest()) {
                break;
            }
            if (ifChange()) {
                direction = direction * -1;
            }
            move();
        }
    }

UML图如下

3、度量分析

可以看到,在处理是否需要开门和是否需要调转方向函数中复杂度较大,这是由于要遍历两个乘客列表,这个操作为了实现线程安全还需要用到锁和try-catch语句,因此嵌套层数较大。事实上我们可以在插入新请求的时候维护两个状态变量来更新当前所需要到达的最大楼层和最小楼层来判断是否需要调转方向,至于是否需要开门,也可以维护一个哈希表来加快运算速度而非每次需要判断时遍历一遍。这些是设计上可以提升的部分。

代码行数上,由于需要实现可捎带,因此elevator类的代码量增加了两倍左右

4、总结

总的来说这次作业主要还是体现在如何实现捎带算法上,由于有两个队列,出于安全性的考虑,在操作任何一个队列时我都要保证另一个队列没人操作,因此申请了一个新的object作为锁保证安全。由于依然是单个电梯,不涉及到把用户分配给哪个电梯的问题,因此依然没有将控制器作为一个单独的线程处理。

三、多部多线程智能(SS)调度电梯的模拟

1、作业内容和思路

本次作业要实现多部电梯的调度,与上次作业不同的点有以下几个,一是每部电梯的速度不同,为了追求速度在分配时应该有所侧重和判断,二是电梯停靠楼层不同,分配时应考虑到这点,三是有些用户请求不能被任何一个单电梯执行,要考虑换乘的问题。关于调度算法没有明确的要求和规定,因此继续延续上次作用的LOOK算法,工作重点落在如何设计控制器部分使得可以完成多个电梯之间的分配。首先, 我先考虑的是关于换乘和可停靠楼层的问题,首先是尽可能让用户不需要换乘,如果有某部电梯能使得用户不换乘就到达,则直接将其分配给这部电梯(虽然也许有时候由于电梯负载等问题,设计为可换乘的也许会节省时间,但是这样设计更加复杂容易出错以及实际生活中人们大多数不愿意换乘),对于不得不换乘的情况,根据其所在楼层和想到达的楼层编码一个换乘站,先送到换乘站再变成一个新的请求从换乘站到终点站,其次,考虑当多部电梯都可以使某个人送到地点分配给哪个电梯比较好,这里采用局部贪心,先假设如果把这个人分配给每个电梯,计算这些电梯的待机时间,选取最早的那个分配,尽可能的减少最大电梯待机时间。判断停机的时候比较复杂,在输入模块关闭后,由于有换乘的可能存在,要同时判断三部电梯中都不存在请求时,让所有线程停止,因此在控制器中维护一个变量用于记录几部电梯是否有在待机状态。

2、UML设计

根据前面的思路,新加入一个myPersonRequest类,继承于原来的personrequest类,包含新的成员变量为trFloor,换乘楼层,同时加入一个方法为nextRequest用于在到站后返回后一段的request,相关代码示例如下

    public MyPersonRequest(PersonRequest p, int f) {
        super(p.getFromFloor(), p.getToFloor(), p.getPersonId());
        trFloor = f;
    }

    @Override
    public int getToFloor() {
        return trFloor;
    }

    public PersonRequest nextRequest() {
        return new PersonRequest(trFloor,
                super.getToFloor(), getPersonId());
    }

而关于控制器,发送request时,需要判断哪个电梯能够尽快送达,在电梯类中加入一个函数用于计算目前情况下若加入新请求还需要多久进入待机情况(若不能够直达则返回-1),后分配给用时最小的电梯,代码示例如下

    public void sendRequest() {
        ArrayList<PersonRequest> reqs2;
        try {
            synchronized (reqs) {
                while (reqs.size() == 0 && !ifFinished()) {
                    reqs.wait();
                }
            }
            reqs2 = (ArrayList<PersonRequest>) reqs.clone();
            reqs.clear();
            for (PersonRequest personRequest : reqs2) {
                int mintime = 1000000000;
                Elevator re = elevators.get(0);
                int t = 0;
                for (Elevator e : elevators) {
                    t = e.preTime(personRequest);
                    if (t < mintime && t > 0) {
                        mintime = t;
                        re = e;
                    }
                }
                re.addRequest(personRequest);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

由于指导书中强调了输出模块不一定线程安全,因此做了一个简单的封装保证输出时是线程同步的,如下

import com.oocourse.TimableOutput;

public class Output {
    static synchronized  void output(String i) {
        TimableOutput.println(i);
    }
}

uml图如下

3、度量分析

和上次作业类似,复杂度最大的方法依然是判断是否需要调换方向和是否需要开关门那里,方法中有对列表的遍历操作。解决方法与前面的类似。

代码量上,由于计算待机时间的函数和运行时的函数类似但有不同,我采用的方法是对其重载了一个布尔参数来表示是在计算时间还是真正运行,因此基本代码量又增加了一倍左右。

 4、总结

这次作业深化了我对多线程之间如何互相唤醒防止死锁的理解。开始时停机那里没有处理好,总是无法正常停止或者丢失乘客,后来才发现是由于没在更新完待机状态后唤醒controller进程和elevator进程,因此在每一个锁上面wait后,当wait的条件有改变的可能的时候都要做notifyall的操作。

四、总结

这组作业在设计上,每次更改的代码量比上一单元有所进步,没有大改的地方(也是因为这几次作业的需求变更不太大),通常是细化一些方法和成员。另外完成这组作业后让我对面向对象有了新的理解,之前认为面向对象只是将成员变量、函数按照一定的逻辑组织起来,现在由于有了多线程的概念,每个对象实体都可以run起来,可以真正的跑自己的代码,与其他对象交互,而不是用面向对象语言来写面向过程的程序。这次作业深化了我对这方面的理解,以后在解决一个问题的时候也可以按照这种思路来拆分不同的线程来解决问题。

猜你喜欢

转载自www.cnblogs.com/zhuo123/p/10759991.html