作业5: 多线程电梯
一开始构想的设计是, 输入一个线程, 3个电梯各一个线程, 调度器一个线程。 输入线程专门处理输入请求, 将合法的请求加入请求队列里。 这次由于是模拟的真实的时间, 同质请求可以直接在输入的时候就给判了。 调度器就是不断的扫描请求队列, 对每一个没有分配的请求看看有没有电梯可以给。 电梯线程就是一个自动机, 自己按照设定的状态运动就行了。 然后就是考虑程序如何结束的问题, 输入控制线程很明显当输入输入结束标志时可以结束, 调度器在输入结束后且请求都分配完毕后可以结束, 电梯就在调度器结束且没有请求分配给它后就是。 设计的大概模式没有什么问题。 后面遇到了一些细节上的问题, 主要是多线程调度的不确定性, 还有就是时间误差的积累。 请求多了之后发现运算的时间误差积累得大了起来可以影响输出了, 于是就在电梯里预存了一个时间, 每次要sleep个3s或是6s的时候,用存的时间算出要sleep到什么时间点, 再更新一下存的时间, 抹掉一些运算误差, 效果好了不少。
![图片名称](https://images2018.cnblogs.com/blog/1346449/201805/1346449-20180501130222349-943155034.png)
![图片名称](https://images2018.cnblogs.com/blog/1346449/201805/1346449-20180501130849841-1545807697.png)
![图片名称](https://images2018.cnblogs.com/blog/1346449/201805/1346449-20180501132130494-298586658.png)
从类图上来看的话请求类由于有各种各样的属性, 相应的方法也很多, 看起来十分庞大, 考虑到分工的问题还可以在细化一下这个类。 从Metric度量上来看, Ctr_new类中的run方法圈复杂度高, 这个类主要是继承上一次电梯的调度器, 基本保留上一次电梯的设计。 Main类中的print方法参数个数高, 这个主要是考虑到多线程的输出, 给控制台输出专门写了个函数放在 Main类里面, 加上了synchronized, 由于各个线程的输出需要的参数不同, 便直接合并在了一起, 其实有很多是用不上的, 可以考虑分割成几个方法。 Elev类的run方法块嵌套深度高, 考虑细分这个函数的职责, 同时避免重复。
这次多线程电梯自己还算写得比较满意的, 公测互测都没有挑出bug。 互测别人都时候就是自己构造小数据, 具体还是测试一些功能上实现是否完备。 由于多线程上答案有许多种, 数据大了的话也不好查看正确性。 对面这个人的代码测某一个大数据的时候发现有点问题, 试图找到问题的所在, 把数据缩小后发现又对了, 看代码也找不出什么原因, 也就没算他错了。
作业6: IFFFT
这次作业的设计思路很简单, 重点学习到的是各种对File类的操作。 主要的线程就是一个请求输入对应一个监控器线程。 直接在Main函数中处理完输入, 根据合法输入请求的个数, 依次打开各个监控器, 监控根据监控模式, 以某个频率保存监控范围内所有文件的信息, 通过比较相邻两次信息的不同点, 根据监控模式作出是否相应。 这一次关于线程结束就是自定义了个让测试者结束所有线程的方法, 因为毕竟监控一个文件本身是没有结束条件的。
![图片名称](https://images2018.cnblogs.com/blog/1346449/201805/1346449-20180501140658480-1670287098.png)
![图片名称](https://images2018.cnblogs.com/blog/1346449/201805/1346449-20180501140704578-532853902.png)
![图片名称](https://images2018.cnblogs.com/blog/1346449/201805/1346449-20180501140710523-1451560854.png)
从类图上看, SafeFile类比较大, 主要是对于File类的线程安全包装, 这个可以理解。 Moniter类属性过多。 从Metics度量上来看, Moniter类的run方法圈复杂度和块嵌套深度都很高。 这个run方法几乎是整个程序的核心部分, 监控器的所有判断, 扫描, 响应工作全都放在这里了, 写得十分面向过程, 重复的代码段也相当多, 我自己也觉得这一段很难看, 可以考虑更加细化分工, 切出几个方法或是新建几个类来分担一部分职责。
关于自己的bug的话, 自己程序本身没有什么问题, 就是测试线程的布置问题, 由于输入不是专门写了个线程, 可能会阻塞测试线程, 所以测试线程只能在输入之前开, 被算了个bug。 别人的代码的话, 做了些简单的功能性测试, 对面也只是犯了个非法情况的小错误, 我也发现了其原因。 如今对于多线程的测试, 由于多线程在执行过程中有偶然性, 我都会找到具体的bug后再从代码上分析具体的原因才往上报。 感觉自己也学到了一些东西,避免犯类似的错误。
作业7: 出租车调度1
这次设计思路和多线程电梯很像, 而且由于有了gui, 很多东西可以用它自带的。 首先输入控制器一个线程, 处理请求的输入, 将合法的请求加入请求队列中。 调度器扫描请求队列, 为每个窗口期内的请求筛选合适的出租车发起抢单, 为窗口期结束的请求筛选合适的出租车进行派单。 100辆出租车一辆是一个线程, 每辆出租车根据当前所处状态按次序行动。 主要的交互就是在于出租车自己是个自动机, 会改变自己的状态, 调度器选定出租车派单, 会从外界改变其状态。 时间误差上沿用多线程电梯的方法, 将误差缩到几乎可以忽略。 对gui中的bfs进行了改进, 将最短路的计算放在了程序初始化, 预处理获得, 较少运行过程中的运算时间。
![图片名称](https://images2018.cnblogs.com/blog/1346449/201805/1346449-20180501144719436-1729894734.png)
![图片名称](https://images2018.cnblogs.com/blog/1346449/201805/1346449-20180501144726146-1529123036.png)
![图片名称](https://images2018.cnblogs.com/blog/1346449/201805/1346449-20180501144730969-1605916995.png)
这次自己犯了个严重的错误, 就是处理输入的时候, new了一个局部变量, 此后所有的输入都是覆盖性的写入, 创建新的请求又是直接把这个局部变量穿进去, 由于java中的这些变量其实都是指针, 导致了请求不断被覆盖, 被测试者弄了3个bug。 自己测试的时候小数据都是间隔很久再输入, 大数据直接另写一段代码直接加的, 就没有发现, 也是吃了个大亏。 给对面测试的时候, 由于公测很少, 就只是一些非法情况, 给对面首先是读代码, 发现了一个功能实现与指导书不符的, 然后试了一个随机大数据压力测试, 发现结果差得很远。 设计原则上没有细究,(本来想加一个显式表达原则, 心太软还是算了。
心得体会
- 线程安全方面主要是多个线程访问同一个数据的时候, 有读有写, 还有多个线程执行IO操作的时候。 要注意用synchronized的加锁来限制, 不过也不要加太多, 影响效率。
- 设计原则方面, 自己主要考虑的还是责任的均衡分配。 把每个类的职责明确一点, 这样的代码看起来清晰, 维护起来也好维护。 显式表达, 懂我原则也是习惯了, 变量名命名有规可循, 自己心里有个数。 重用原则也是自己一直在注意的, 这样可以减少代码的冗余。 其他的原则在今后的学习中也会逐渐习惯成自然。