综述

主要任务就是写一个电梯模拟器,读入每一个人的请求然后让电梯把他们送到想去的地方。

从第一次到第三次作业,三次的主要任务都是相同的,但是每次都增加了很多的细节,每次的难度都逐步增长,电梯复杂度和瞎跑度都大大提高;同时我们也对于多线程的设计、写法、调试也逐渐有了心得和经验。

以综合性最强的第三次作业为例。它要求的电梯和一般的普通的常见的电梯,又有很多不同:首先这玩意是目标选层电梯,在进电梯之前就先选好楼层,然后坐等电梯送你去;然后这玩意开关门十分鬼畜,开门一瞬间(即使只有一条缝),我们的乘客都可以从这一条缝挤进电梯;再然后这个电梯好像是支持全自动的:假如A电梯能从1楼到3楼(但不到5楼),B电梯能从3楼到5楼(但不到1楼),这时候一个想从1楼道5楼的乘客,他只需要在1楼设置好楼层,然后进A电梯,随后我们的电梯系统能够自动完成换乘的事情,而根本不用乘客去考虑:我需要先到3楼然后摆摆腿出来换乘。这就好比你从沙河上了昌平线然后地铁上睡了一觉,原地醒来就到了知春路。

我们呢先不管这些逻辑上的不熟悉,来考虑一下电梯的设计。

需求

设计一个电梯系统,包含三个电梯,名为A、B、C,他们的可停靠楼层、最大载客量、上下层速度都不一样。可停靠楼层如下图所示:

要求对于所有不同时输入的请求,都有相应的调度,能够在最短时间内送人到达目的地。

设计

输入处理:利用官方给的阻塞式端口输入,然后判断这个请求能不能由一台电梯独立解决;不行则拆解成两个请求。比如从-3到3的请求,我就拆解成-3到1,再从1到3。然后将请求加入一个队列。

电梯调度:调度器为每个空闲的电梯分配一个合适的任务,让他去跑。那么什么叫“合适的任务”呢?就比如本题的C电梯,它的合适的请求就是出发楼层和到达楼层都在C电梯的可停靠楼层范围内的那些请求,比如1-3、5-15,但像2-4、6-14就不是了。当然对于一组有先后顺序的请求,比如一个原本为从-3到3的请求,被拆解成-3到1,再从1到3——在-3到3完成之前,1到3也不算一个“合适的”请求。

捎带和被捎带问题:设计一个楼层类Floor,用来记录在着一层楼等待的乘客。电梯每到一个可停靠楼层,我们就先把想要这一层楼离开电梯的兄弟给踢走,然后在着一层楼中先找到那些能捎带上的兄弟,把他们带上。如果更详细地再分析一下,这其实还包含着主请求的变更,比如你在1楼接了一个FROM-1-TO-3,然后在2楼顺了一个FROM-2-TO-5的兄弟;那么电梯到达了3楼之后,把第一个兄弟踢走之后需要把主请求修改为FROM-2-TO-5,好让电梯接着走。

输出:电梯每执行完一步操作就对应输出“arrived”,“open”,“close”等。

多线程的设计

设计五个线程:主线程(兼输入)、调度器线程、三个电梯线程。调度器与主线程之间通过线程安全的“队列”进行交互,调度器与电梯之间直接通过调用异步的方法进行通信。

历程

重构前:对于每个请求,我都随机派一个电梯去起始楼层 x 接人。而这分为两种情况:若该电梯能到达 x ,开门就完事了;要是到不了,再把这个“去楼层 x 接人”的请求放回队列。假如成功接到了人,电梯也还要动脑子:这个人想去3楼,我能不能到3楼呢?哎呀,好像不行。那,我又该在几楼把他放下来呢?我是不是该通知C电梯,让给他帮我一手,把这兄弟送到目的地。

重构后:拆解请求,让前文“想”的这个过程交给调度器,把任务分解、分配给傻瓜电梯,这样电梯就不用动脑子去想我该在哪里把这个人放下来,而是简单机械地执行调度器给的请求就好了(对电梯和程序员都友好)。

重构是件很纠结的事情:本来写了快一千行,但是好像又不满意,bug越写越多;重构吧,又怕遇到新的问题白白浪费时间。

结果

前两次作业没有问题,就是老老实实按照指导书算法写,正确性没有什么问题。

第三次作业嘛。重构一下,就出事了:因为最大楼层不知道为什么写成了19(显然20才对啊,真是蠢),然后导致第20层楼捎带的时候会爆炸,表现在“这个人莫名其妙地在19楼进了电梯”,所以就爆炸了。出现这个问题,确实应该怪自己,怪自己畏惧近千行代码没有敢去一行行看,去找逻辑错误;怪自己没有想到用随机生成数据去做覆盖性测试,不然这样的问题肯定能发现。

性能分析

类图:

方法复杂度:

(仅显示复杂度较高的方法)

明显可以看出disassemble方法(也就是那个拆解一个请求为两个所用的方法),由于包含了大量的if-else语句导致设计复杂度很高,但是本质复杂度很低。

另外两个复杂度大头相信大家也差不多:线程的run()方法,好像没什么方法来做有效的优化。

illegalFromFloor()方法中,采用了很多类似于

if(A == true) return true;

这样丑陋的代码,导致本质复杂度很高。

lift()方法由于包含了很多与捎带有关的和与电梯状态的修改,但是写法又是基于简单的for-if,所以本质复杂度不高但是设计复杂度比较高,所以这也是容易产生bug的地方,需要程序员仔细检查代码逻辑。

类复杂度:

改进和SOLID原则分析

  • 有些方法(参考性能分析-方法复杂度)的本质复杂度较高,体现的是程序员对语言的的不够熟练。
  • 有些特殊情况不能正常退出。
  • 电梯和调度器有些功能的分离还不是很彻底,因为有一些方法到底是属于电梯好还是属于调度器好,我还没有定论。有少许违背单一责任原则(SRP)原则。
  • 对于扩展不是很方便,虽然利用了工厂来建立新电梯,使得第四台电梯的加入十分方便,但是假如有其他“反人类”的需求比如乘客1不喜欢A电梯非要坐B电梯,那么对于既有的代码不能够保持原封不动。有违单一责任原则(OCP)原则。
  • 没有类的父子关系。不违背里氏替换原则(LSP)原则。
  • 高层模块“调度器”对于不同的底层模块“电梯”都能够同样地执行。符合依赖倒置原则(DIP)原则。
  • 仅使用了Runnable一个接口。不违背分离原则(ISP)原则。

心得体会

通过这三次作业,逐渐地了解了多线程设计的思想。从第一次作业的毫无头绪、仿照课件,到查阅锁的使用,再到研究设计模式和SOLID原则,自己的代shi码shan也逐渐地OO了起来。对我来说,几大难题分别是:多线程的同步控制和电梯逻辑的控制。对于多线程的控制是通过了大量的本地测试和根据例子进行仿照,后来才开始系统的了解锁的使用,确立哪些东西需要加锁哪些不需要,哪些需要保护哪些不需要;线程之间可以有哪几种通讯的方式。另外一个难点是多线程环境下对于每个线程的逻辑debug。我写的电梯逻辑一开始是充满了许多bug的,后来是通过”走一步print一步“的思想来肉眼跟踪找到bug的。业界有人认为:多线程是程序员在工作中遇到的最大的魔鬼。在这三次作业中,我也感受到了,因为多线程的bug隐藏的一般很深,有些bug产生的条件也比较苛刻,甚于充满偶然性。

OO随笔之纠结的第二单元——多线程电梯的更多相关文章

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

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

  2. BUAAOO第二单元多线程电梯作业总结

    第二单元多线程作业需要保证线程安全

  3. OO第二单元多线程电梯总结分析

    一.概述 这一部分的作业考察的关注点与上一次的作业有所不同,上一次的考察重点主要集中在输入输出的判定以及多态的考察上面,而这一次是让我们进行多线程程序的调度与开发.这次开发过程中最大的感受就是自己之前 ...

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

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

  5. 「BUAA OO Unit 2 HW8」第二单元总结

    「BUAA OO Unit 2 HW8」第二单元总结 目录 「BUAA OO Unit 2 HW8」第二单元总结 Part 0 前言 Part 1 第五次作业 1.1 作业要求 1.2 架构设计 1. ...

  6. oo第二单元——多线程魔鬼电梯

    在初步认识了面向对象思想后,立刻进入了多线程的学习,本单元的难点主要是锁的理解,需要保证线程安全的同时防止死锁的发生,也要尽可能缩小锁的范围,提高性能.这一单元以电梯为载体,让我们从生活出发,从电梯运 ...

  7. OO第二单元总结——电梯调度问题

    一.设计策略. 在三次作业中,多线程程序的实现分以下几个步骤: 1. 主线程Main类的创建多个线程. 2. 共享对象的synchronized锁保证线程之间的互斥访问. 3. 采用notifyAll ...

  8. OO第二单元总结——电梯

    在电梯系列的作业中,笔者的整体架构几乎没有发生改变.现介绍如下,对于一个电梯系统,主要的工作步骤就是获取乘客请求.分派请求.执行请求.针对这样的工作模式,笔者设计了Elevator.Uselist两个 ...

  9. OO随笔之魔鬼的第一单元——多项式求导

    OO是个借助Java交我们面向对象的课,可是萌新们总是喜欢带着面向过程的脑子去写求导,然后就是各种一面(main)到底.各种方法杂糅,然后就是被hack的很惨. 第一次作业:萌新入门面向对象 题目分析 ...

随机推荐

  1. 搞懂 ZooKeeper 集群的数据同步

    本文作者:HelloGitHub-老荀 Hi,这里是 HelloGitHub 推出的 HelloZooKeeper 系列,免费开源.有趣.入门级的 ZooKeeper 教程,面向有编程基础的新手. 项 ...

  2. Baolu Aggregate TPS Report

    1.说明 这是一个基于JMeter官方的Aggregate Report的监听器改进而来的监听器!!! 2.插件背景 早在很久之前,宝路就曾经改造过JMeter的Aggregate Report 的源 ...

  3. Android学习之 AlertDialog

    •AlertDialog简介 AlertDialog 可以在当前界面弹出一个对话框: 这个对话框是置顶于所有界面元素之上的,能够屏蔽掉其他控件的交互能力: 因此, AlertDialog 一般用于提示 ...

  4. 计算机体系结构——CH2 指令系统

    CH2 指令系统 右键点击查看图像,查看清晰图像 X-mind CH2 指令系统 数据表示 定义 指计算机硬件能够直接识别,可以被指令系统直接调用的那些数据类型 确定哪些数据类型用哪些数据表示实现,是 ...

  5. python3使用kivy生成安卓程序

    技术背景 虽然现在苹果占据了很大一部分的市场,但是从销量数据来看,安卓还是占据了人口的高地.这里我们介绍一个用python的kivy+buildozer来进行安卓APP开发的简单教程,从整个过程中来看 ...

  6. .Net Core 路由处理

    用户请求接口路由,应用返回处理结果.应用中如何匹配请求的数据呢?为何能如此精确的找到对应的处理方法?今天就谈谈这个路由.路由负责匹配传入的HTTP请求,将这些请求发送到可以执行的终结点.终结点在应用中 ...

  7. django+x-admin管理后台模板开发管理后台案例(设计部分)

    使用django+x-admin管理后台模板搭建管理后台 一.环境需求 1.django:3.1 2.python:3.7 3.x-admin:2.2 4.pycharm:2020.3.2 5.ubu ...

  8. OO_Unit 3 JML规格化设计总结

    OO_Unit 3 JML规格化设计总结 JML语言概述(Level 0) 概念定义   JML(Java Modeling Language)是用于对Java程序进行规格化设计的一种表示语言.JML ...

  9. 无线网络的应用之aircrack-ng

    在kalilinux的aircracke-ng中.在这儿描述自己所遇到的问题并给予写blog 在使用之前,需要确定是否有对应的支持无线网卡监听的网卡,在虚拟机中需要先将网卡的驱动重定向到虚拟机内 在终 ...

  10. 网络编程Netty IoT百万长连接优化

    目录 IoT推送系统 IoT是什么 IoT推送系统的设计 心跳检测机制 简述心跳检测 心跳检测机制代码示例 百万长连接优化 连接优化代码示例 TCP连接四元组 配置优化 IoT推送系统 IoT是什么 ...