P1 设计结构

三次作业的架构都没有较大的改动,基本上都是靠调度器接受输入的请求并放入队列,然后调度器根据不同的电梯的当前状态来把请求分配至不同电梯的请求队列中,最后电梯再根据自己的请求队列去运行。因此接下来都以第三次的作业为例来进行讨论。

一、diagram

  • MainClass类

    负责创建并启动调度器线程和所有的电梯线程,然后不断地读取输入并送至调度器。
  • ControllerAll类

    能够读取所有电梯的信息以及所有的输入请求,然后根据电梯的状态来对请求进行分配。其成员变量requests为共享资源数据。
  • Elevator类

    负责保存某个电梯的状态信息。
  • ElevatorThread

    负责保存某个电梯的主请求与副请求队列,并实现了电梯的运行算法。

二、UML框架图

接下来由框架图大致演示一下线程之间的交互

三、设计策略

资源分类

  1. 共享资源

    • ArrayList<PersonRequest> requests

      负责保存全局的乘客请求,然后由调度器去分配这些请求给相应的电梯。由于考虑新请求的产生可能会使得请求分配最优解的变化,因此要使用再分配机制,即给每个电梯的副请求(未进入电梯的乘客请求)分配时,并不会将这些请求从requests总请求中剔除,当且仅当请求从电梯的副请求转移至主请求时(乘客进入电梯),才将requests中对应的请求剔除。
    • ArrayList<Elevator> elevators

      负责为电梯运行部分和任务调度部分提供相应的电梯状态信息。

      任务调度部分主要对电梯状态进行扫描读取,以便衡量如何分配请求。

      电梯运行部分主要是对电梯状态去进行改变。
  2. 非共享资源
    • ArrayList<InElevatorPersonRequest> mainRequest

      保存在电梯运行线程类中,作为电梯的主请求(即目前在电梯内的乘客请求)。通过副请求向主请求的转移(乘客上电梯)增加请求,通过乘客下电梯而删减请求。
    • ArrayList<PersonRequest> secondRequest

      保存在电梯运行线程类中,作为电梯的副请求(想要上该辆电梯的乘客请求)。通过调度器的任务分配而改变请求,以及向主请求中转移而删减请求。
  3. 不变资源

    主要定义在Define类里,由作业要求定义而来,如楼层数、电梯运行速度、电梯最大载客量等。

多线程协同和同步控制

  1. 加锁原则

    由于两个共享资源都有可能被进行读写操作,因此在任何要扫描共享资源或者改写共享资源的类的地方均上锁。对于仅读取或改写一个共享资源的方法添加相应类型的锁;对于要读取或改写两个共享资源的方法,采用先对requests加锁,后对elevator加锁的方式,以避免死锁的产生。

  2. wait和notify控制

    • 用户输入

      若有新的乘客请求,假如requests队列,并notify()调度器线程。

    • 调度器分配

      若请求为空或者给所有电梯分配完一次请求后,进入wait()。

      待读取新的请求或者某个电梯的主副请求队列发生改变时,被notify,然后进行任务分配,notify其它所有的电梯线程。

    • 电梯运行

      若主副请求均为空,进入wait()。

      待调度器分配任务后被notify,然后运行电梯。

调度算法

  1. 调度器请求分配算法

    • 流程图

    • 部分优化特点

      • 采用重分配原则,即通过“乘客挑选最近电梯”、“电梯从选择该电梯的乘客中挑选最近的乘客”两个步骤分配完第一个电梯后,第二个电梯的“乘客选择该电梯”的请求可能会变化(因为可能相较于前一次分配,会把前一次没被电梯选择的请求加入到这个请求队列中)。若不采用这种方式,性能会大大折损,例:

        将请求1,2,3分配给A,B电梯->第一次分配,请求1,2,3均选择电梯A->电梯A选择最近的请求1->由于1,2,3均选择了A,B无请求可挑选
      • 采用直达优先原则

        即纵使一辆电梯离某个请求最近,若其不能满足乘客直达,乘客依然不挑选这辆电梯。
      • 分配请求不超过电梯最大人数要求

        记录当前电梯状态,为其分配副请求直到电梯内人数加副请求数量等于电梯最大人数限制为止。
  2. 电梯运行流程

    • 流程图

    • 部分优化特点:
      • 将开关门独立于需要锁的changePeople函数之外(负责上下人的函数块)。使得此电梯休眠期间别的电梯可以进入需要此锁的函数中。。
      • 换乘乘客楼层选择:

        记乘客当前搭乘电梯为A,其它电梯分别为B1,B2,B3,选择能到达该乘客最终目标楼层的电梯,不妨是B1,B2,分别计算A能到达的楼层与其能到达的楼层的交集,不妨设为Z1,Z2,从Z1,Z2中选择目前离该乘客搭乘电梯当前楼层最近的楼层作为该乘客的换乘楼层,从而确定换乘电梯。(但是不考虑其它电梯的状态的话,这样的换乘方式某些情况还是比较低效)

四、可拓展性

若要拓展的方面不脱离 “用户输入->调度器调度->电梯响应”这样的结构的话,本代码至少不会达到难以修改的地步,因为添加部分代码要求并不会对除了调度算法和电梯运行算法之外的地方产生影响,函数块的耦合度较低(虽然类之间的耦合度很高),而调度函数块要面临着较大部分的改动,因为这部分算法要做到全知全能,才能对电梯进行任务分配,如第二次作业到第三次作业,新增了电梯楼层限制,那么该函数中请求挑选合适电梯的部分就要完全重写。但是由于这几次作业都在上述的结构限制内,因此更改起来还是比较容易的。

倘若突破了上述限制的结构,那扩展性几乎是0。

五、设计结构度量分析

下面放Designite Java的结果(虽然我感觉这部分对我来说非常的没有用处,因为纵使告诉了我哪些地方有问题,我也比较难以在下次的编写中特意避免这些问题,只能起到“责怪我这次代码写的不好”的作用,而真实的启发,即如果实际去避免这些比较少)

方法复杂度:

这里挑选了几个代码行数和圈复杂度较高的方法,然而我看了一下,很大的原因是因为我没将电梯按类型去编写类,导致判断电梯类型的时候要用大量的if-else,但是我感觉这一部分无可厚非,采用这样的方式反而能够更加的直观一些。

依赖关系:

P2 bug分析

  1. 自我bug分析

    虽然强测和互测中均为找的我的bug,但是我还是觉得我程序有些地方的书写很奇怪,感觉许多地方没有产生bug是由一部分的特判强撑着,稳定性很差。

    最让我感觉奇怪的地方就是之前为了优化乘客上下部分而将开关门从这部分提取到函数外面,更改乘客的部分就由原本的整体变成了"判断是否开关门->开门->上下乘客->关门",而判断是否要开关门与上下乘客的部分原本应当是衔接的,这两部分都要保持共享数据requests的不变,这样就可能导致出现由于期间共享requests的改变,导致任务重分配,导致本来应当在此楼接的乘客分配到别的电梯中去的情况,即"判断应当开门->开门->(世界线变动)空操作->关门"的情况。

    不过还好这几次作业并不把无意义的开关门作为错误的点,而导致我能够苟过,而且由于现阶段的电梯特性好像不会出现上述空操作的情况。但是这依然是一个潜在风险,例如电梯的可换乘楼层随时间变化的话,可能就会在变换后导致空操作的产生。

  2. 发现别人程序bug所采用的策略

    使用评测姬挂一天就完事了。由于本人知识与能力有限,不会写评测机程序,因此白嫖别人的评测姬。

    (特此鸣谢:pbnb!)

P3心得体会

这个单元的作业让我理解到了设计架构的重要性,以及向他人学习的重要性。第一单元的作业由于架构的混乱+不吸取他人经验+无脑的上手就开写导致我的作业屡次遭重,而且耗费了大量的时间。但是这次由于参考了某些学长的UML框架图(其实就是架构),以及电梯运行的算法,因此写起来作业感觉轻松多了。

这个单元也让我明白了:

必要与熟练的知识基础+良好的设计架构+提前的较为完整的构思 = 一份轻松且质量较好的代码。

oo第二单元博客总结的更多相关文章

  1. OO第二单元博客

    三次作业的设计策略 第一次作业 多线程协同控制 第一次作业只需要两个线程和一个公共缓冲区: 负责读取输入并把它添加进命令队列的线程,即生产者 负责从命令队列中取出命令执行的线程,即消费者 再加上一个缓 ...

  2. OO第二次博客作业—17373247

    OO第二次博客作业 零.写在前面 OO第二单元宣告结束,在这个单元里自己算是真正对面向对象编程产生了比较深刻的理解,也认识到了一个合理的架构为编程带来的极大的便利. (挂三次评测分数 看出得分接近等差 ...

  3. OO第二次博客作业——电梯调度

    OO第二次博客作业——电梯调度 前言 最近三周,OO课程进入多线程学习阶段,主要通过三次电梯调度作业来学习.从单部电梯的傻瓜式调度到有性能要求的调度到多部电梯的调度,难度逐渐提升,对同学们的要求逐渐变 ...

  4. OO第二次博客作业(第二单元总结)

    在我开始写这次博客作业的时候,窗外响起了希望之花,由此联想到乘坐自己写的电梯FROM-3-TO--1下楼洗澡,然后······ 开个玩笑,这么辣鸡的电梯肯定不会投入实际使用的,何况只是一次作业.还是从 ...

  5. OO第二次博客作业--第二单元总结

    第一次作业 1. 设计策略 第一次作业,一共三个线程,主线程.输入线程和电梯线程,有一个共享对象--调度器(队列). 调度的策略大多集中到了电梯里,调度器反而只剩下一个队列. 2. 基于度量的分析 类 ...

  6. 北航OO(2020)第二单元博客作业

    第二单元第一次作业 多线程设计策略 第一次作业的想法是设计三个线程:输入线程,调度器线程以及电梯线程.输入线程获取请求并发送给调度器线程:调度器线程通过查询电梯线程的状态(等待.停靠以及移动),并综合 ...

  7. 渐入OO课的深处,探索多线程的秘密——OO第二次博客总结

    一次又一次的挑战,一次又一次全新的知识,我来到了多线程的面前 第五次作业 1.度量分析 >第五次作业由于很大程度上调用的是前两次电梯的一些代码,所以存在的问题与前几次也十分相似.同时由于第一次使 ...

  8. 2019年北航OO第二次博客总结

    一.多线程电梯系列作业设计策略 1. 第一次作业——"FAFS傻瓜电梯" 第一次作业是先来先服务的"傻瓜电梯",我当时觉得这个设计未免太简单了,于是就在傻瓜电梯 ...

  9. oo第二次博客-三次电梯调度的总结与反思

    本单元从电梯调度相关问题层层深入,带领我们学习并运用了了多线程相关的知识. 三次电梯调度依次为单电梯单容量.单电梯可携带.多电梯可携带. 一.我的设计 在第一次作业中,使用了最简单的FIFO调度方法. ...

随机推荐

  1. 没想到即将上线的NGK生态应用这么厉害?!

    话说这即将上线的NGK公链可不是闹着玩的,这条公链的蛰伏时间长达两年,恐怕这个准备时间,连最初的区块链1.0时代的项目都无法比拟,现在的话那都差太远了. 编程一段代码并不难,难的是耐得住赚快钱的心.人 ...

  2. spring框架aop用注解形式注入Aspect切面无效的问题解决

    由于到最后我的项目还是有个邪门的错没解决,所以先把文章大概内容告知: 1.spring框架aop注解扫描默认是关闭的,得手动开启. 2.关于Con't call commit when autocom ...

  3. 对Innodb中MVCC的理解

    一.什么是MVCC MVCC (Multiversion Concurrency Control) 中文全程叫多版本并发控制,是现代数据库(如MySql)引擎实现中常用的处理读写冲突的手段,目的在于提 ...

  4. fork后子进程与父进程的关系

    共享代码空间,各自独立数据空间,子进程初始化数据是父进程的复制. 资料参考: https://blog.csdn.net/u013851082/article/details/76902046

  5. Java Socket编程基础及深入讲解

    原文链接:https://www.cnblogs.com/yiwangzhibujian/p/7107785.html 原文写的特别好,非常详细,但是禁止转载,那我就不再复制粘贴了! socket实现 ...

  6. HashMap什么时候进行扩容?

    Threshold:table数组元素个数size的大小超过threshold且且Node<K,V>[] table数组长度没有超过64时时table数组扩容.当hashmap中的元素个数 ...

  7. Svelte 码半功倍

    你未注意到的最重要的指标. 注意:原文发表于2019-04-20,随着框架不断演进,部分内容可能已不适用. 所有代码都有 BUG,你写的越多,BUG 越多,这很合情合理. 同时,写的越多,费时越多,留 ...

  8. Tango with django 1.9 中文——3.Django基础

    让我们开始运用Django.本章主要是给你一个关于创建新项目和新应用过程的概览.在本章的末尾,你将建立起一个简单的由Django驱动的网站. 3.1 配置测试 让我们测试以下你的Python和Djan ...

  9. 力扣119. 杨辉三角 II

    原题 1 class Solution: 2 def getRow(self, rowIndex: int) -> List[int]: 3 ans = [1] 4 for i in range ...

  10. 剑指 Offer 51. 数组中的逆序对 + 归并排序 + 树状数组

    剑指 Offer 51. 数组中的逆序对 Offer_51 题目描述 方法一:暴力法(双层循环,超时) package com.walegarrett.offer; /** * @Author Wal ...