多线程电梯问题单元总结

前言html

  在继上次表达式求导的三次做业后,我又完成了多线程电梯问题的三次做业,感官上,多线程的做业难度要弱于表达式求导,可是实际上,多线程电梯问题,由于考虑到线程间的通信,以及线程安全,实际上要困难一些。安全

 

设计策略数据结构

  第一次做业——单个无调度策略的目的选层电梯:多线程

  第一次的做业只是作一个没有任何调度策略的目的选层电梯,所以在设计上并无动脑子(捂脸),采用单例模式建了一个名为User的类用于存储输入的请求队列,建了一个名为Elevator的线程类和名为Main的主函数类,输入是在主函数里完成的,采用的策略就是,在主函数中建立并启动电梯线程,而后主函数不断地往User单例里面写数据,电梯不断获取请求,就是一种很是傻瓜的写法。架构

 

  第二次做业——单个采用ALS(捎带策略)的目的选层电梯:模块化

  此次做业我采用的策略是让电梯一层层地前进,而且由电梯主动询问调度器是否有用户能够稍带,而在电梯地运行轨迹上我遵循楼道间通常电梯地运行方式,即当电梯上行地时候,不接受任何下行地请求,同理,下行不接受上行地请求,所以电梯将会在楼层间不断地上下,理论上对于必定地用户数量,这个调度方法拥有一个固定值。函数

  为了实现这个策略,我创建了用户类和楼层类,两个类之间用Arrarylist结构联系在一块儿,在查询的时候,顺序为该楼房第几层,第几个用户的请求是什么。在此基础上,我在Scheduled类(调度器类)里创建了两个楼房队列,一个表示上行,一个表示下行,同时我把Input线程从Main中摘了出来,Main只承担初始化和启动线程的工做。而在Evelator类里,电梯有一个属于本身的队列,这个队列对应楼层的每一层,存储目标为该层的用户,同时,电梯的行为仅有本身控制,也就是说Input和Scheduled没法直接修改电梯的运行方式。电梯与Input之间经过Scheduled创建联系,Input获取外界请求,Scheduled负责把数据分类处理,Evelator从Scheduled中请求数据,判断线程状态等。性能

  整体上来讲,我经过Scheduled将Input和Evelator基本上彻底分隔开,所以在实际操做的过程当中,我只保护了Scheduled的读取和修改方法就实现了线程的保护。学习

 

  第三次做业——多个采用ALS(捎带策略)的带有不一样属性的目的选层电梯测试

  相比于第二次做业,此次做业加入了电梯的限制,包括电梯的停靠楼层限制、人数限制、不一样的楼层运转时间。可是本质上,和第二次做业并无大的差距,第三次做业,我重构了用户类,第二次做业用户类我采用了字符串存储数据,显然,这种数据模式对于使用类的人来讲并不友好,所以我在User类的下一层加入了Contaner类,该类用于应对分层时可能出现的多条语句,剩下的架构基本和第二次保持一致,只作了以下的修改,Evelator类在第二次的基础上添加了属性初始化,Main函数增长了一部分初始化数据的生成,Scheduled类里增长了须要换乘乘客的静态处理方法。

  在线程安全的处理上,由于增长了换乘乘客,所以在限定线程结束上修改了一部分,其余并无变更。

  总结:

  我的感受此次做业,从第二次开始,架构就设计得比较好了,基本实现了总的设计思路,即电梯负责跑,输入负责输入,调度器负责数据处理,把类的功能基本上实现了独立和分割,这使得我第三次做业较轻松地完成了。而在线程安全上,我使用的是synchronized,前后尝试了锁方法,锁类,锁类的一部分,锁对象的一部分,最后为了保险采用了锁整个类的方法,不得不说这种锁的确很低效。除此之外,除了第一次做业采用了单例模式,我后两次做业均使用了传递参数的形式,我始终以为单例模式不够直观。

 

基于度量分析代码

ev(G)基本复杂度是用来衡量程序非结构化程度的,非结构成分下降了程序的质量,增长了代码的维护难度,使程序难于理解。

Iv(G)模块设计复杂度是用来衡量模块断定结构,即模块和其余模块的调用关系。该数值越高,说明模块的耦合度高,难以复用。

v(G)是用来衡量一个模块断定结构的复杂程度,数量上表现为独立路径的条数,即合理的预防错误所需测试的最少路径条数,圈复杂度大说明程序代码可能质量低且难于测试和维护,经验代表,程序的可能错误和高的圈复杂度有着很大关系。  

注:上述引用自http://www.javashuo.com/article/p-hrljazti-hd.html

 

第一次做业 :

  类图:

  

  度量分析:

       

  自我点评:

  从上图,很清晰,个人第一次做业设计很是简单,就是单纯为了完成做业在main里,并且方法数量很是少,在evelator里面甚至只有两个方法,一个是为了简化sleep的函数,另外一个就是run,能够说除了简单明了以外,这个设计一无可取。

 

  根据SOLID原则分析:

  个人三次做业都不会涉及里氏替换原则和接口隔离原则,由于我压根没有使用继承和接口。而仅就此次做业分析,开闭原则几乎没有遵循,若是我想在此次做业基础上实现后几回做业,那么重构是必然结果,而单一功能更不可能,由于我只有三个类却实现了电梯,调度器,还有输入,显然电梯等的功能,根据单一功能原则应该分离开。

 

第二次做业 :

  类图:

  

  度量分析:

  

  自我点评:

  从度量分析图来看,个人此次设计不考虑类,只看方法,方法的耦合度至关低,预防错误所须要的测试路数也很是少,只有少部分方法在维护的难度上存在问题,从数据来看,此次的设计至关成功。从UML类图能够清晰地看出个人调度器的类的结构,我把调度器,输入,电梯,以及主函数的功能彻底地分开了,彼此之间实现了彻底的独立功能。可是,此次的设计实际上局限很大,此次做业我当时并无考虑扩展性,所以在Onewayticket这个类中,把多个用户数据用字符串的形式结合起来,这种设计,理论上适应性很是高,但实际上,对代码的维护和改变影响很恶劣,这是我当时没有考虑的。

 

  根据SOLID原则分析:

  这次做业相比于第一次,在单一功能原则上实现了大的突破,虽然没有达到标准的一个类一个功能,但至少,一个方法一个功能,实际上若是利用相似于宏的结构化,代码的模块化能够进一步增长。关于开闭原则,User涉及的类实现的较为糟糕,主要缘由在于字符串的拓展着实麻烦,可是对Evelator类的实现能够说彻底符合开闭原则,具体将在第三次做业介绍。里氏替换原则和接口隔离原则,仍是没有涉及相关内容。

 

第三次做业 :

  类图:

  度量分析:

  

  自我点评:

  第三次做业其实是第二次做业改的,做业二改(做业三)继承了做业二全部的优势,而且在此基础上,关于User的改进使得User得到了更好地拓展性,同时User的数据形式使数据的得到和分析更加简洁。在设计之初实际上考虑过工厂模式,可是感受对于只有三个电梯没有必要,实际上若是尝试一下,代码应该会简洁不少。在度量分析里,有个叫作gettranfer的方法,圈复杂度太高,实际上问题并非处在这个方法上,而是出在schedule这个系列的方法,这个方法的添加主要用于用户的换乘,里面写的是很麻烦的换乘方式,这里我采用的是静态的逻辑,实际上,这种设计不管是性能,仍是实现都是下等,惟一的优势就是不怎么费脑子,应该采用带权图之类的东西作更好一点。

 

  根据SOLID原则分析:

  由于第三次做业又能够叫作第二次做业改,因此这部分分析是同样的。个人设计在开闭原则上实现的很是好,对于schedule类,不考虑重构底层数据结构形成的转变,我只添加了一个用于用户换乘的方法,而对于Evelator类,我只添加了一个初始化构造方法,能够说,我认为实现的已是很是好了。

 

自我BUG分析

  本次电梯系列做业,我遇到的BUG种类很是少,三次做业有两次进入互测没有被HACK,还有一次由于重大逻辑错误,致使没有进入互测。BUG分为两类,一类是线程安全及协助BUG,还有一类是逻辑BUG。

 

  线程安全及协助BUG:

    public boolean endthread() {
        synchronized (this) {
            if (waitforup == 0 && waitfordown == 0) {
                if (sign && waitfortranfers == 0) {
                    return true;
                }
                try {
                    wait();
                }
                catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            if (waitfortranfers == 0
                    && waitfordown == 0 && waitforup == 0 && sign) {
                return true;
            }
        }
        return false;
    }

  这个方法位于第三次做业的Scheduled类,从第二次做业,全部的安全控制都在Scheduled类中,上面的代码做用是判断当前的电梯线程是否须要挂起或者结束,上面是最终的正确版本,这个BUG我改了两次,一次是没考虑中转的用户进入电梯后还有可能出来的问题,这使得用户还没出电梯,换乘电梯的线程就已经结束了;第二次是,改了第一个BUG后,电梯线程无法正确结束。

 

  逻辑BUG

    private User schedule(int id,int from,int to) {
        ArrayList<Container> temp0 = new ArrayList<Container>();
        int fromele = whereyouare(from);
        int toele = whereyouare(to);
        int tranfers = 3;
        Boolean reverse = false;
        if ((fromele & toele) == 0) {
            tranfers = gettranfers(from,to,fromele | toele);
            if (from < tranfers && to < tranfers) {
                if (from > to) {
                    tranfers = 3;
                }
                reverse = true;
            }
            Container container = new Container(tranfers,ele(fromele));
            temp0.add(container);
            Container container1 = new Container(to,ele(toele));
            temp0.add(container1);
            waitfortranfers += 1;
        }
        else {
            Container container = new Container(to,ele(fromele & toele));
            temp0.add(container);
        }
        User temp = new User(id,temp0,reverse);
        return temp;
    }

 

  这部分代码只是从Scheduled类里的schedule系列方法的一部分,这里出现了一个重大的逻辑BUG。我采用的是二进制编码来判断人员的出入电梯和换乘思路,而后我从逻辑上忽略了一种可能,这使得我再过了中测了,强测直接GG,只得了4分。

 

如何发现别人的BUG

  实际上,这部分我并无合理地策略,我没有像那些大佬同样搞了自动评测机,并且由于忙于OS和本身的私事,实际上并无多久的时间搞互测,互测采用的仍是之前的老思路,先拿出本身的测试集,而后再编写一些逻辑上可能出错的测试集,测试逻辑和线程安全问题。因为此次时间不足,我甚至没有细看代码,针对性地编写测试集。下一次做业能够考虑编写一个自动评测机来实现自动化评测了。

 

心得体会

  这三次做业我的感受,操做简单,理论复杂,设计苦难。多线程问题最突出的问题是线程安全问题。对于这个问题我体会良多。第一次做业,本质上是用多线程完成单线程任务,这里线程安全问题并不明显;第二次做业,线程之间共享资源的互斥是个巨大地问题,我从这里才开始学习synchronized的用法,我前后尝试了在不一样的类里面锁相同的对象,可是,这种模式会使得检查安全问题的时候很是混乱,几乎不具备逻辑性,因此后来我把全部的锁都放到了方法里。最初的设计,为了减小锁形成的性能损失,所以我尽可能锁了对象,并且锁的对象都很是细,这在结束线程的判断上出现了语法问题,由于结束线程理论上须要获取一整个完整对象的锁,结果锁的对象不一致,形成了矛盾,实际上这个问题,我以为能够用一个锁的对象的记录解决,可是当时为了方便和安全,改为了锁一整个对象,这部分仍然能够获得改进。

  其次,大概就是设计原则上的问题了。我认为一个类的全部行为应该都算它的方法,并且,对于这系列做业,并无其余的类具备相同的电梯的功能,所以我并无彻底按照单一功能原则实现,但对于大的类,类的功能是彻底不重叠的,而对于类的内部,针对这一系列做业,采用了低耦合的作法,并且遵循了开闭原则,这使得我第三做业的完成至关轻松,基本上就是copy。

  再次,关于程序正确性的检测,此次我感慨良多。第三次做业得益于第二次做业的完备设计轻松地完成,这使得我并无针对第三次做业进行覆盖性测试,只进行了部分逻辑的弱测和推演,结果致使出现重大逻辑失误,使得强测几乎没有分数,而实际上的改正只用了6个字符,这使我在难过之余也开始正视覆盖性测试的必要性,还有,一我的最大的敌人就是懒惰和傲慢,只有谦虚才能前行。

相关文章
相关标签/搜索