前言:

  第二单元总共包括三次电梯调度作业。这三次作业在笔者看来是为了让学生了解什么是多线程,多线程的好处及可能存在的潜在问题,对于多线程的安全问题应该如何解决和保证结果的唯一性和正确性。那么接下来笔者将结合三次电梯调度作业来谈谈在这三次作业中我都收获了哪些。

 

第五次作业:

  结构分析:

  

  

  

  代码分析:

  第五次作业相对来说结构比较简单:Main方法调用静态对象调度器,然后作为参数传入输入和电梯实例出来的两个对象中,最后开启这两个线程。唯一需要注意的就是对调度器中任务链表进行操作的时候一定要记得加锁,也就是在同一时刻只有一个线程能够访问到这个链表,确保不会因为多线程而导致结果错误。这一次采用的调度方式是服务最先到来的指令,好处在于代码容易实现,缺点在于效率低下。

第六次作业:

  结构分析:

  

  

  

  代码分析:

    第六次作业相对来说要“智能”一些,因为需要使用一定的调度策略,防止总运行时间过长。此时笔者采用的策略是当电梯没有运转的时候从指令链表中读一条指令,并不取出,根据这一条指令确定一个目标楼层,也就是电梯接下来要运行到那个楼层,那么因此也就确定了一个运行方向。确定了之后就开始运作,并在运行过程中每一层都进行一次遍历(包括出发楼层):是否有同方向且在本楼层上电梯的人,因为还没有考虑载客量,因此一旦有这样的需求,那么就捎带,更新目标楼层,继续运行。

  在写代码的时候笔者便意识到一个问题,就是如果有个人要从15楼到14楼,但是在电梯运行的过程中不断有人要从1楼到2楼,2楼到1楼,那么如果只考虑性能应该是先将1楼2楼的需求处理完之后再去15楼处理第一个请求效率是最高的。但是笔者认为这样的电梯十分“鬼畜”,很容易造成饥饿——即15楼层的请求迟迟得不到处理,然而明明这个请求是最先到来的。笔者觉得这个从实际的角度考虑并不符合实际,所以并没有为了性能分去优化这个地方,而是遵从现实。一味的迎合性能分,我觉得并不一定是一件好的事情。但是笔者的调度策略也并不完全符合现实。假设电梯现在上行,在电梯运行到目标楼层之后,应该判断目标楼层上方是否还有请求,处理完目标楼层之上的请求之后才应该反向,但是我在运行到目的楼层后仍然重复了最开始的思路,从指令链表中读一条指令重新确定运行方向和目标楼层,我觉得这个点是一个值得优化的地方。

  当然上述看法仅代表笔者的观点,每个人的代码思路和原则并不一样,尊重每一个人的想法。

第七次作业:

  结构分析:

  

  

  

  

  代码分析:

  第七次作业最难的地方笔者认为是调度策略。笔者所用的方法是,将23个楼层设定每个楼层的“归属”,比如1楼的归属为ABC,3楼的归属为C,对于A电梯利用二进制的方式表示为100B,B电梯为010B,C电梯为001B。设定一个类,构建一个私有静态对象,这个对象可以通过方法确定给定的两个楼层是否有相同的归属,很显然如果有相同的归属那么使用归属电梯即可以一步到位。如果没有相同的归属那么就需要换乘,笔者的方法是寻找换乘楼层:顺着该指令运行方向寻找这样的一个楼层:和起始楼层和终止楼层都有至少一个相同的归属,那么这个楼层便可以作为换乘楼层。得到换乘楼层之后就可以把原来的指令变成两个指令进行执行。然后每一条指令都确定了一个电梯来执行,将它放在对应的电梯指令链表中即可。

  有几个特殊的点需要考虑,第一个就是如果是4楼到3楼那么根据这种方式找到的换乘楼层是1楼,但是实际上5楼更加合适,同理,2楼到3楼寻找到的是5楼,但其实是1楼更加合适。这两种情况需要特判一下。还有就是诸如1楼到15楼这样有多部电梯都可以直接到达的情况下,应该怎么取舍。笔者所用的方法是,如果人员已经进入电梯,那么就会把这条指令从调度器对应链表中删除。对于有多种电梯可以选择的情况我们会优先选择目前链表长度最短的电梯,如果链表长度相同那么就优先选择A电梯,再选择B电梯,最后选择C电梯。之所以这么考虑是根据运行的速度。不过后来在讨论课听别的同学的分析,优先级改为A>C>B应该平均效率会更高一些。仔细分析可能和C能够到达的楼层有限,那么很多时候A和B都处于负载的状态而C并没有需要执行的指令或者能够执行的指令并不多,那么有些优先给C便可以分担A和B电梯的压力,整体来看缩短等待时间和运行时间。还有就是如果将指令分割成两个指令之后就有一个顺序问题:一定是前一条指令执行完成后才可以执行后一条指令。对于这个问题笔者使用一个和指令链表同步插入删除的阻塞状态链表。对于普通的指令阻塞状态设为0,对于拆分的指令,前一条指令设为1,后一条指令设为-1。那么很容易知道,我们取指令的时候只能去阻塞状态为0和1的指令。那么-1的指令什么时候结束阻塞呢?一旦有人从电梯中出去,我们就根据他的ID在三个电梯指令链表中查询是否有相同ID的指令,因为如果是普通指令,那么一个ID对应的就只有一条指令,这个时候就不会在三个电梯指令链表中查询到此ID,但是对于拆分的指令,利用前一个指令的ID便可以查到当前正被阻塞的指令,然后将其阻塞状态改为0,这条指令便可以像普通指令一样被取出了。

  解决了策略问题其实还有一些小的问题,比如笔者使用的是while(arraylist.size == 0){wait()}加notifyall()的方式。但是对于这次作业会出现一个问题,就是“轮询”。虽然wait()加notifyall()正常情况下是不会出现轮询的,但是如果出现一条3-16楼的指令时,拆分后就有一条15-16的不可取的指令。那么A电梯中arraylist.size() == 0这个条件就不会满足了,就不会进入循环进行wait(),造成了轮询的情况出现,导致CPU时间过高。解决此办法也比较容易。如果取不出指令时便返回null,如果是取出来的指令是null且不是我们手动输入的结束符号的null,那么便睡眠等待100ms再尝试去取。谈到null,笔者的线程结束方式也比较简单:当输入的是null的时候,将null插入到三个链表中,然后如果取到的是null且当前链表长度为1的时候证明是时候结束此电梯了,置符号endSign == 1,在每一个循环中判断,如果endSign == 1,变返回调用它的方法或终止循环。这样可以确保电梯不会提前下班,同时可以正常结束。

关于SOLID原则:

  

  SRP:有了第一单元的锻炼已经初步掌握了OO的思想,在本单元也基本能符合SRP。

  OCP:基本了解哪些需要设为private,哪些设为public。 

  LSP:三次作业中都没有使用继承,因此本原则没有体现。

  DIP:三次作业中都没有使用继承和抽象类,因此本原则没有体现。

  LSP:三次作业中都没有使用接口,因此本原则没有体现。

BUG分析:

  本单元强测和互测中都没有出现bug,在写的过程中出现的bug也已经在上面代码分析中列出并给出对应的解决办法。

分析别人BUG时运用的策略:

  首先搭建评测机,随着作业的推进评测机的重要性逐渐凸显。然后测试一些自己再写代码时特别注意的点,或者说自己差一点遗漏的细节,那么别人也可能在这个地方出现问题,那么针对这些问题构建测试数据,查找他人bug。不过不同于第一单元的作业:第一单元的作业很多时候是抓输入输出的错误,而第二单元由于实现了输入输出接口统一化,所查的bug多是逻辑思考上可能出现遗漏的地方。

心得体会:

  最开始的时候可能受上机实验的影响,对于不确定的地方全部加锁,这种解决办法也被调侃道“遇事不决就加锁,锁成单线程”。如果是为了解决作业问题这种方法基本是有效的,但是既然本单元就是为了让我们对多线程和线程安全有比较熟悉的掌握,那么这种方式显然有悖于老师助教们的初衷。因此在自己写代码的时候会首先分析哪些变量是共享变量,怎样的操作可能引发线程安全问题。投机取巧可能一时有效,但是最终吃亏的很可能是自己(想必很多第二次作业使用通用公式法的同学在写第三次作业的时候重构得快哭了吧)。完成作业固然重要,但是能够用更好的方式来实现,对我们的益处应该是更大的吧。

OO第二单元学习总结的更多相关文章

  1. 【OO学习】OO第二单元作业总结

    OO第二单元作业总结 在第二单元作业中,我们通过多线程的手段实现了电梯调度,前两次作业是单电梯调度,第三次作业是多电梯调度.这个单元中的性能分要求是完成所有请求的时间最短,因此在简单实现电梯调度的基础 ...

  2. OO第二单元小结

    OO第二单元小结 一.三次作业代码分析. 1.第一次作业 第一次作业是单部电梯的傻瓜调度,由于其过分傻瓜,所以第一次作业我只有两个类,一个main,一个电梯,main类负责不断从输入流中读取命令,如果 ...

  3. 电梯也能无为而治——oo第二单元作业总结

    oo第二单元作业总结 一.设计策略与质量分析 第一次作业 设计策略 在第一次作业之前,我首先确定了生产者--消费者模式的大体架构,即由输入线程(可与主线程合并)充当生产者,电梯线程充当消费者,二者不直 ...

  4. oo第二单元作业总结

    oo第二单元博客总结 在第一单元求导结束后,迎来了第二单元的多线程电梯的问题,在本单元前两次作业中个人主要应用两个线程,采用“生产者-消费者”模式和共享数据变量的方式解决问题.在第三次作业中加入多个电 ...

  5. OO第二单元优化博客

    OO第二单元优化博客 第五次作业没有性能分,但是,我在这一单元的宗旨就是写一个日常生活中 最常见的那种电梯,所以第五次我没有写傻瓜电梯,而是直接写了个\(look\),和第六次基本相同. 总计一下lo ...

  6. OO第二单元多线程电梯总结

    OO第二单元多线程电梯总结 第一次作业 设计思路 Input为输入线程,负责不断读取请求并将读到的请求放入调度器中. Dispatcher为调度器,是Input线程和Elevator线程的共享对象,采 ...

  7. 2020北航OO第二单元总结

    2020北航OO第二单元总结 前言 本单元考察基于多线程的电梯调度问题,成功让我从一个多线程小白到了基本掌握了使用锁来控制线程安全的能力,收获颇多(充分体验了迷茫地de一个又一个死锁bug的痛苦). ...

  8. OO第二单元——多线程(电梯)

    OO第二单元--多线程(电梯) 综述 第二单元的三次联系作业都写电梯,要求逐步提高,对于多线程的掌握也进一步加深.本次作业全部都给出了输入输出文件,也就避免了正则表达式判断输入输出是否合法的问题. 第 ...

  9. OO第二单元作业总结【自我反思与审视】

    第二单元作业的完成史,就是一部心酸的血泪史…… 多线程的出现为我(们)打开一片广阔的天地,我也在这方天地摸爬滚打,不断成长!如果说第一单元之前还对Java语法有所了解的话,那么这单元学习多线程则完全是 ...

随机推荐

  1. MyBatis学习总结-MyBatis快速入门的系列教程

    MyBatis学习总结-MyBatis快速入门的系列教程 [MyBatis]MyBatis 使用教程 [MyBatis]MyBatis XML配置 [MyBatis]MyBatis XML映射文件 [ ...

  2. 6.2 集合和映射--集合Set->底层基于链表实现

    在6.1中我们实现了底层基于二叉搜索树的集合,本节就底层如何基于链表实现进行学习,注意:此处的链表是之前自己封装的. 1.集合set相关功能 1.1 add()的不同 用于链表本身没有去重的效果,因此 ...

  3. 修改select默认小箭头

    在html中select下拉框默认的小箭头是这样的 有时候我们需要把这种小箭头用更好看的图片代替,就需要改变样式了. html 代码如下: <select class="comm-se ...

  4. QAC静态测试配置及使用教程

    使用前提:安装成功QAC软件. . 1.打开软件如上 . 2.file->Auto-Create-Project,出现如下所示对话框 1-工程名字 2-将要分析的代码路径 3-代码报告输出路径 ...

  5. 一文读懂PRBS定义、生成办法、作用

    对于眼图测试.误码率和抖动容限测试,最常用的测试码是PRBS,主要有PRBS7.PRBS15.PRBS23和PRBS31.本文主要解释了PRBS的定义,生成方法以及简单应用. PRBS定义 二进制序列 ...

  6. (转)get和post的区别

    Http定义了与服务器交互的不同方法,最基本的方法有4种,分别是GET,POST,PUT,DELETE.URL全称是资源描述符,我们可以这样认为:一个URL地址,它用于描述一个网络上的资源,而HTTP ...

  7. POIUtils 读取 poi

    依赖: <!-- ############ poi ############## --> <dependency> <groupId>org.apache.poi& ...

  8. 18Linux-LNMP-Linux就该这么学

    LNMP 编译环境包: [root@linuxprobe ~]# yum install -y apr* autoconf automake bison bzip2 bzip2* compat* cp ...

  9. MTK6261 11C之Init Analysis【手记】

    初始化流程 Application_Initialize systemInitialization(); HWDInitialization(); USC_Start(); OSTD_Init(); ...

  10. 主成分分析、实例及R语言原理实现

    欢迎批评指正! 主成分分析(principal component analysis,PCA) 一.几何的角度理解PCA -- 举例:将原来的三维空间投影到方差最大且线性无关的两个方向(二维空间). ...