• 面向对象课程多线程总结


    注:不清晰的图片右键浏览器打开就能放大啦orz

    第五次作业——多线程电梯

    基于度量的程序结构分析:

     

    优缺点:

    劣势是可以看出控制器线程类还是掌管了大局,没有好好拆分掌控好局部分工,优势是逻辑比较清晰。

    设计原则:

    显式表达原则做的不好,很多地方是用返回-1表示状态,这个习惯需要改进。

    设计策略分析与心得体会:

    这是笔者第一次接触多线程这个概念,刚开始准备做的时候简直一脸懵比生无可恋,一直怀疑能不能写出来。经过这次作业,笔者最大的体会是如果没有写这次多线程电梯,恐怕自己不能真正的掌握面向对象思想。其中的过程是曲折的,每次都觉得自己已经在靠近最佳的设计方案了,但是总能讨论出更多的问题,于是一步步寻求解决方案,一步步打磨设计思路,在经过三天的痛苦设计之后,周一才开始动手写代码,好在前期的努力没有白费,终于在周二写完并开始debug。周二晚上看到结果正确显示的时候,真心开心到爆。对于设计思路,笔者有很多想记下来的想法。

    首先,第一次接触多线程,最想问的一个问题大概就是要对什么对象开线程?开了线程以后里面要做什么工作,用什么数据结构组织呢?交互对象是什么呢?线程安全问题发生在哪里呢?

    笔者建议先分析因果关系与过程的独立性。开线程是为了模拟真实世界,而真实世界表现为因果论的产物,不妨以电梯调度为例,电梯的主体工作是运行,调度器的主题工作是取请求、根据请求调度电梯,逻辑关系是电梯受调度器的支配。因为电梯运行与调度器给电梯分配请求这两件事是需要分开进行的,所以要针对每个电梯开线程,针对请求模拟器开线程。继续分析,IO处理应该是与调度器分开的,即调度器只负责看有没有新的请求加进来,而IO等待请求的输入的请求模拟器应该是另一个线程(因为调度是需要时时刻刻进行的,而请求模拟器可能因为等待输入而被阻塞),这个时候调度器和请求模拟器通过请求队列这个共享对象进行交互就可以。这个时候你会发现,在搞清楚为什么开这些线程之后,每个线程负责的工作就被分成了各个模块,下面要做的就是分析各个模块的任务。

    请求模拟器线程:读入请求,判断请求的有效性,有效则放入请求队列。

    调度器线程:需要一个主队列来记录所有要执行但是没被分配给电梯的请求。每次调度需要完成以下工作。1、从请求队列查询请求模拟器新加入的请求(如果没有就返回空)。2、根据楼层灯是否被点亮判断所有的同质请求,如果同质就丢出主队列。3、根据每个电梯的主请求与电梯当前的位置判断捎带请求并分配给相应电梯,如果可以分配就丢出主队列。4、这时主队列剩下的请求就是既不同质也不能捎带,意味队列头着需要找空闲电梯完成。这时,如果有空闲电梯,就把这条请求给空闲电梯并将其升级为该电梯的主请求,这时要立即进行下一次调度来判断新升级的主请求能否捎带主队列里面剩下的请求。

    电梯线程:需要自己的执行请求队列(也就是调度器为电梯准备好的捎带队列),每隔规定的时间sleep结束后查询本楼层是否要执行请求。在主请求被执行完之后,查询捎带队列是否为空,不为空表示还有ER请求,这时选取最早产生的ER请求升级为主请求(电梯自己完成主请求的升级);为空表示所有要执行的请求都已经执行完毕,将状态置为空闲,表示向调度器寻求主请求(需要调度器调度一个主请求)。

    这些分析完毕后,还差分析共享对象交互的安全性。显然这里的共享对象是请求队列和电梯。请求模拟器对请求队列进行写入操作,调度器对请求队列进行读入操作。电梯线程对电梯的状态进行操控,调度器对电梯执行队列和主请求进行写入操作。最重要的是,这些操作都是需要上锁和同步控制的。(笔者第一次写的时候没经验没有加synchronized导致读写不同步de了两个小时的bug,哭晕)

    完成这些分析以后,思路就变得很清晰啦,就可以开始coding了。

    其实回头看看,这些思路都很正常,但是当时怎么那么难想呢orz。可能大概也许是因为前几次作业都是用的面向过程的思路直接调度的,思路转变需要时间。还有可能是协调的框架不够清晰,没能把各个部分的功能拆开来看,然后再对各个对象进行功能分析与同步分析。

    分析自己程序的bug

    这次作业因为经过了比较严密的思考以及和小伙伴儿不断的讨论,架构上和功能上都没有被发现bug。这里不由的感叹一句,团队合作真的是一件很享受的事情。

    分析别人程序的bug

    这次拿到的程序bug有点小多,再加上笔者为了测试自己的程序弄了很多比较刁钻的测试集,还是发现了一些问题。就对方的代码来看,同质的判断有问题,捎带时主请求的升级有问题,升级后捎带的判断有问题,细节的地方与线程同步的地方还是需要更多的考虑。笔者还阅读了被测试者的代码,发现会有没考虑数据边界的问题等等。

    第六次作业——IFTTT文件管理系统

    基于度量的程序结构分析:

    优缺点:

    劣势是monitor控制器里面的函数嵌套过多,优势是函数清晰明了,逻辑简洁易懂。

    设计原则:

    信任原则做的不够好,函数的调用封装还需要进一步改善。

    设计策略分析与心得体会:

    这次作业的思路明显比上次更加清晰,再加上对多线程有了进一步的了解,理论上是更好完成的。但是事实上,这是笔者觉得目前为止最困难的一次没有之一。这次作业对于笔者来说只有三天时间,但是有几乎一天半的时间都在阅读指导书、讨论区和微信群,毕竟指导书对要求的说明很模糊,容易产生误解,比如文件到底什么情况下就不需要追踪之类的问题。笔者觉得真正写代码的时候IFTTT的工作量也很大,而且边写边会出现更多的问题,这些问题看起来简直是不收敛的。写完bug(划掉)代码之后,debug的经历也是最痛苦的一次,因为笔者总会有各种各样报null point使用空指针的crash,于是得出了一个经验,以后用变量之前先在代码里面判断一下变量是否为null,这样更方便bug的定位。

    分析自己程序的bug

    这次的bug都跟指导书的要求有关,公测错了两个,第一个是忽略了指导书关于“线程对象最多开启10个”的要求,因为笔者考虑的是可能一个对象会有4个触发器,每个触发器又会有3个监控任务,所以最多是能开启120个线程,就直接计数到了120。但这样是很有问题的,直接计数120会导致不同对象也能开120个线程,应该针对对象直接计数而不是直接计数120;第二个是modified触发器输出到文件的内容少了size变化的记录,这个是因为笔者想当然的认为只要记录引起触发器变化的内容就行,而忘记了输出所有的文件信息,笔者对指导书要求的输出内容理解有歧义,希望以后的指导书能针对输出的内容给一个明确的要求。互测错了一个,是跟上面提到的null point crash有关。

    分析别人程序的bug

    这次抽到的代码是大佬的,写的内容非常简洁但是功能基本完备,值得细细阅读学习一下。有一点小缺憾是没有对path-changed的文件进行进一步追踪,笔者认为这个是指导书有点没说清楚。还有一个incomplete是因为没有提供创建文件夹的方法,感觉这个也是指导书没强调但是公测给了一堆需要新建文件夹的测试用例。希望以后指导书把功能要求写的更清楚一点。找bug的时候笔者是根据分支树的要求进一步展开测试的。

    第七次作业——多线程出租车调度第一次作业

    基于度量的程序结构分析:

    优缺点:

    劣势是出租车线程因为有状态机的转化工作与运送乘客的工作,功能嵌套过于复杂。优势是各个类的分工明确,比较均衡。

    设计原则:

    应该遵循显式表达原则,把输出的状态用字符串含义表达。

    设计策略分析与心得体会:

    出租车调度的指导书比较完备,这次作业调度的整体框架有点像电梯调度的。重点是taxi线程和controller线程的实现。

    ControllerFlashController两个控制器类是调度的核心,前者实现在窗口结束时对该请求分配最佳的出租车,后者实现在窗口期不断为请求寻找合适的出租车队列,这样设计的目的是为了实现高内聚、低耦合的思想,两个任务的调度都需要时间,没有逻辑上的等待关系,所以开启两个调度器实现设计。Taxi类用状态机实现状态的转化,并负责在接到乘客后按照规则运行。线程间的交互体现在控制器根据请求队列的状态与出租车的状态进行控制与分配,对象的交互主要发生在出租车、请求队列与控制器之间。因为请求队列是交互对象中扮演“Tray”角色的类,请求队列里面的方法有为请求模拟器提供的放置方法,也有为控制器提供的取出方法,这里涉及数据的交互,因此RequestQueue的数据结构采用线程安全的LinkedBlockingQueue来组织,来解决线程交互间的安全问题。在Taxi类中,可能会发生多个请求被分配给多个Taxi的情况,这时askRequest(判断当前出租车是否有要执行的请求)方法和addReq(给对应出租车分配对应的请求)方法可能同时被多个Taxi对象访问,为了保证线程安全,这些方法都要加synchronized来保证线程安全。同理,taxi类的其他方法也需要上锁,来保证同一时间只有一个对象进行写操作,避免发生线程并发时出现读写不一致的玄学问题。Request类里的candidate是该请求在时间窗口内找到的能响应的出租车集合,因为添加与取出会有线程安全问题,也要加锁。

    分析自己程序的bug

    这次的程序没有功能性bug,但是被扣了设计原则的“显式表达原则”,测试者认为出租车的state状态用数字表达不是显式表达,建议用枚举类型。他说的不无道理,但是枚举类型本质也是数字表达,更何况笔者在代码里以及注释说明了各个数字的含义,而且在代码的使用过程中state都是以IDLETODSTWFSTOSRC表达的,只不过输出到文档的时候直接用了数字,笔者觉得这样就有点较真的了。

    class STATE{

    static final int IDLE = 0;  // 停止状态

    static final int TODST = 1;  // 送乘客去目的地状态

        static final int WFS = 2;  // 等待服务状态

    static final int TOSRC = 3;  // 去往接乘客的状态

    }

    分析别人程序的bug

    这次抽到的程序很有趣,被测试者针对每个请求都开了一个线程。我想,他这样做的考虑可能是想要严格控制3s时的时间窗口,但事实上这样做有很大的风险,首先他的目的不一定能达到,因为JVM的线程调度时间不是我们能控制的,而且这样会造成多个请求同时访问出租车状态时因为请求数过多而造成等待时间反而更长的问题,而且最最重要的是,针对请求开这么多线程,而且没有限制请求的数量,我的eclipse直接被他跑崩了orz。笔者一次性输入了100条指令,直接就报了heap满和内存满的crash,然后eclipse基本上就是卡死状态了。还有就是被测试者的输出有问题,没有直接取当时的快照输出,导致判断看起来有问题,而且乘客位置也有问题。还有一个就是公测没有判断请求起点和终点是不是同一个。

  • 相关阅读:
    MySQL执行计划解读(转载)
    排序算法
    Linux下在防火墙中开启80端口、3306端口
    Android APN
    PB之——DropDownListBox 与 DropDownPictureListBox
    CSS总则。
    WIN7系统中设置默认登录用户
    Javascript日期比较
    myeclipse中UTF-8设置
    webview loadUrl() 弹出系统浏览器解决办法
  • 原文地址:https://www.cnblogs.com/lxqiaoyixuan/p/8976202.html
Copyright © 2020-2023  润新知