BU AAOO 第二单元作业总结
😵😵第二单元作业总结
多线程的协同和同步控制🔒
第一次作业
周二第一次听多线程的课,周三考生产者-消费者模式,周六ddl前就要完成第一次多线程作业,属实算是速成多线程。等到我中测所有点全✅了,我其实还不明白我为什么都对了。感觉完成第一次作业的过程更倾向于照猫画虎,缺乏对多线程系统的了解
-
有哪些线程,共享哪些数据?
-
【输入线程】 x 1(生产者),共享【缓冲队列】
-
【顾客线程】 x n(产品),共享【运行信息】
-
【电梯线程】 x 1(消费者),共享【缓冲队列】【运行信息】
-
【调度线程 】x 1(调度),共享【缓冲队列】【运行信息】
为什么会给每个顾客单开一个线程?一是符合认知,人是个活物,电梯开了上不上实际上应该交由人自己来判断。但在第一次作业中我的人还是一个虚假的线程,完全受电梯支配,在自己的run中就只有等的动作。电梯来了不管上下行都把他拽进去,到地方了再把他扔出来。准备第二次作业再做调整
-
-
有哪些类,如何处理冲突?
-
主类Main
计时开始 -> 创建【运行信息】-> 创建【缓冲队列】-> 创建并启动【输入线程】 -> 创建并启动【电梯线程】-> 创建并启动【调度线程】
-
运行信息Message
为什么设置这个类?
考虑到进入电梯后的人和电梯的楼层信息和方向信息完全一致,而调度算法唯一需要获取的有关电梯的运行信息就是楼层和方向,故我将这两个东西抽离出包装成这个类
该类下设两种构造方法,一种用于电梯,一种用于顾客。在实例化一个顾客时,顾客一定处于电梯外的状态,此时的Message记录其目标运行方向以及当前所处楼层。一旦顾客进入电梯,其运行方向与楼层与电梯完全一致,我们可以认为是将自己托管给了电梯。因此只需将原先顾客的Message替换成电梯的Message,即达成了共享。
- 写:只有调度线程会写,而且调度只有一个线程
- 读:调度、电梯、缓冲队列和顾客会读。调度线程内读写不会冲突,缓冲队列、电梯和顾客只会在电梯静止时读,不会冲突。
经分析,不需要加锁。
-
顾客Person
顾客是对PersonRequest的封装,
赋予了只有需求和欲望的他以肉体。我本来是要给顾客附加Message的,但后来发现本次作业不需要,为了尽量减少不必要的bug,我删去了。使得顾客成了封装了数据,添加了方法的一个虚假线程。 -
输入Input
扮演生产者的Input使用输入接口读入,对缓冲队列进行写操作。阻塞式输入十分省事。需要注意的是在输入结束后向缓冲队列发出结束信号,也算作写操作。
-
缓冲队列BufferQueue
由于不会使用线程安全容器,故手动加锁。
每层设置一个上行等待队列,一个下行等待队列。还设置输入停止标志和共享电梯运行信息。
- 写
- 写等待队列:生产、消费动作都会写队列。生产动作是在任意时间都可能写,消费动作已知在电梯静止开门状态写。要加锁!
- 写结束标志:输入线程任意时间结束都会写结束标志,可能会与读产生时间差,要加锁!
- 读
- 读等待队列:因为只在调度里读,为了防止死锁在调度统一上锁
- 同时读等待和结束标志:同上
- 写
-
电梯接口Operator
搞了一个纯净的接口,他除了方法声明什么都没有,感觉处理的有点生硬?
-
电梯Elevator
作为消费者拥有停止标志、电梯里装载的乘客队列、电梯门、共享运行信息和缓冲队列。
给整个run方法加锁,但只在电梯门开时抢线程,其余时间锁由调度拿着。也就是说只有这两个家伙抢电梯的线程,分工明确的话问题不大。具体流程如下(所有不满足条件的都意味着进程结束):
运行中的电梯门处于关闭状态 -> wait -> 如果是因为电梯停止运行被唤醒则进程结束,否则意味着调度完毕,电梯门开了 -> 把到站的乘客扔出去,把这站等的人不管上楼下楼都拽进来 -> notifyAll, wait -> 再次被唤醒,回到开头循环
电梯线程逻辑很简单,只负责在门开了后
通过量子引力和斥力把乘客玩弄于故障,其余时间被调度玩弄于股掌。非run部分也要注意加锁- 写
- 开关门会写门的状态,且需要sleep,这必然要加锁,上下行也要sleep,🔒!
- 写停止标志,🔒!
- 读
- 读电梯里乘客队列,其实加了🔒肯定没事,但是还是思考一下。写队列都在门开的时候,读队列都是在关门状态,不构成冲突。
- 读停止标志的时候在加锁的run里,无事
- 写
-
调度LOOK算法无脑版
掌控着电梯、缓冲队列和运行信息,主要控制这三者读写,自身不进行具体读写任务,因此不必关注加锁问题,属于核心算法部分。
主要算法是两头跑,见人就拉,管你在电梯里呆多久,能给你安全送到不错了,详述如下(同样,条件不满足就结束关电梯):
锁缓冲队列 -> 如果电梯还干活且没任务可做 -> wait缓冲队列 -> 缓冲队列被输入线程唤醒。如果是因为结束而唤醒就关电梯结束,反之交锁继续 -> 锁住电梯 -> 如果本层有任务就开门 -> 把锁给电梯抓人 -> wait电梯 -> 抓完人关门 -> 本层无任务了交锁 -> 如果本方向无任务就转向,有任务就超方向走一层 -> 回到开头
-
-
设计点评:
我又干一傻事儿,以上是边梳理边改,然后我在周六21点45改完,(冷却时间15分钟)还敢交吗?😁
第一次写多线程,我梳理上面哪些地方要加锁哪些地方不用还用了挺长时间。所有的🔒都是用synchronized,还不会使用线程安全容器和读写锁等工具。整体安全性没问题(我强测教的是未梳理版,和这个差不多,好像确实是没有发现什么线程安全问题的)
但是!我在调度中控制电梯的开关!即,在一个线程中访问另一个线程。这使得我三次迭代作业都犯了这个错误。前两次没出现什么故障,第三次会出现神奇的CTLE。其实可以沿用这样的设计框架,但是使用消息传递方法异步控制电梯线程的动作。如果我重修,我就这么写🙃
第二次作业
第二次作业与第一次大体有以下不同:
- 电梯楼层增加负数楼层,但不存在0层。只需要做个简单的楼层映射函数即可
- 电梯数量从1到5不定。想偷懒很容易改好,想优化算法需要动动脑子。
- 增加电梯最大载客量。本身实现较易,但涉及到算法设计。
可以看到,如果想偷懒的话,只需要造n个电梯、n个调度、n个缓冲队列,在输入的时候把乘客按照一定的算法合理分配到不同缓冲队列里,其他部分微调就可以轻松完成此次作业。
其实选取这个思路并不叫偷懒,这本身也是一种合理的而且体现了迭代的思想的实现方法。但是如果不加思考、不加权衡,因为懒得动脑子想其他办法而直接采用这种思路,就是真的偷懒了。(标准哲学思考句式:“xxx本身不...,但...就...”)
我经过权衡后确实选择了各组独立调度的做法,具体的分配算法在【功能设计与性能设计的平衡】模块展现。
从线程设计角度来说第二次作业有以下改动:
-
顾客Person
不再作为一个单独线程,其行为完全交给电梯控制
-
输入线程Input
输入线程承担知晓电梯个数、创建电梯、调度、队列等原先由Main主类完成的任务,同时承担了分配算法的实现,其责任一下变得较大。
-
将所有共享变量的🔒都换成了线程安全容器、原子操作以及读写锁
设计点评:
能够迭代是很好的,因为我第一单元的函数作业基本上就是在不停的大改,约等于重构。原来迭代代码量这么少啊😭
但是我写第三次的时候发现了第二次的算法bug,也不算bug,我把某处的运行方向搞反了,按理性能应该比不优化还差才对。但可能是负负得正,我多错了几处反而还达到了目的?
这次作业尽可能的构造了线程安全类,把阻塞队列、原子类型、读写锁都用到了,安全性应该也没什么问题,至少扛住了同屋大佬的评测姬
第三次作业
第二次作业与第一次大体有以下不同:
- 每个电梯到达楼层不同
- 每个电梯运行时间不同
- 动态增加电梯
很多人采用全局等待队列和局部等待队列的方法,但我想回到最初的起点,重新让一个人单独当一个线程,把选择什么电梯的权利交给人。即这个人的线程会判断自己该加入哪部电梯的等待队列,自行判断换乘的细节。
以下是迭代的改动
-
BufferQueue
static isAllBufferEmpty() 用以统计是不是所有人的线程都结束了,就拿人来统计比较容易,人线程开始时addBuffer(),结束时 subBuffer()
-
Look
本身调度思路和上次没什么区别,但是因为CTLE改的乱七八糟
-
Elevator
电梯在功能上没什么区别,只不过演变成了父类,下继承ABC三个电梯子类。
关于线程的继承我研究了一会,发现没啥特殊的,继承就完事了。
-
小trick:
A跑得快,A给运到离目的地近的地方
C跑得慢,C给运到运动距离最少的地方
-
-
Message
-
功能:
封装电梯运行状态
等待队列关心的是电梯的方向、电梯楼层、电梯号。
电梯中的人关心的是电梯的楼层和电梯号
-
成员变量:
- 读写锁
- 电梯方向
- 电梯号
- 电梯所在楼层
-
-
Person
-
功能:
挑一个合适的等待队列然后把自己托管,被放出来的时候看一看到站了没有,没有就找下家
要选择托管电梯,就需要获得所有电梯,信息,等待队列的相关信息。虽然有点费吧(我也不知道费啥,但是管他呢)
-
成员变量:
- 需求
- 托管的电梯的信息
- 当前楼层
- 中转楼层(可以等于终点)
- 终点楼层
-
-
Input
创建电梯、信息、等待队列、乘客,并把前三者传给乘客,相比于第二次作业不再负责全局分配。
-
点评:
第三次作业太懒了,攒了很多事情一直拖着不想写。缺乏设计直接动手,迭代的非常不优雅。因为CTLE的问题把代码改的千疮百孔。最后也不做测试,出了个弱智功能bug,不说了越说越难受...
其实我觉得用人来判断不失为一个可行的思路,如果我重修的话,如前文所述会用消息传递法优雅的调整调度和电梯两线程之间的纠缠。
线程安全方面因为沿用前两次的框架,也没发现什么问题
功能设计与性能设计的平衡⚖
-
【思路】乘客自行挑选电梯,各电梯独立调度
其实就是上文提到的最容易迭代的实现思路。分组的线程设计使得各部分独立运行,相互之间不存在线程冲突,安全性很高。换成的实现也比较容易,除非被运往目的地,乘客一直循环选择电梯即可。我认为这种思路无论是实现的难易还是安全性都十分理想,剩下需要关注的就是性能问题。
决定性能的关键是如何选择电梯。
我姑且称他们为算法吧,看上去高大上,其实就是憨憨思路
-
【算法】均匀分配
以(电梯内人数 + 等待队列人数)为指标,将新来的顾客分配到指标最小的队列中。
优:简单
缺:粗暴
因为平均的思想在内,性能应该不会太差。但是没有耍小聪明,故对某些情况也不会太好。是中规中矩的稳妥办法。
-
【算法】寻找最短达时间
假设有n个电梯(即n个组),每个新到来的顾客在 i 层,那么大概有以下四种情况

因为开门+关门的时间刚好等于上下一层的时间,于是可以将400ms看作一个时间单位。主要想法是预计哪个电梯会最快翻他的牌子,就把他分到哪个电梯的后宫里,如果电梯都满员就沿用
雨露均沾均匀分配方法。对于情况①,预计到达时间(ArriveTime) = 电梯满员 ? MAX : (楼层差(j - i) + 沿途楼层开关门次数(电梯中 i 到 j 层等待出去的非空队列的和))
对于情况②,预计到达时间(ArriveTime) = 电梯满员 ? MAX : (2 *(最高目标楼层 - i)-楼层差(j - i) + 沿途楼层开关门次数(电梯中 i 到 j 层等待出去的非空队列的和))
优:当前新来的顾客需求会较快被满足
缺:会出现”插队“现象。因为顾客一旦被分配到某个电梯的等待队列中就无法再”反悔“,这时新来的顾客可能因为”插队“打乱了拖后了它原来的被翻牌子时间,它如果换到别的队列里早就结束了,在这边只能望眼欲穿、苦苦等待。
很简单的道理,”各扫门前雪“,新出一个问题谁离得近谁解决它。实现方法并不难,但涉及到对各个电梯信息的计算,需要控制好线程安全(在实现的时候我发现其实不必,算法部分出现数据偏差并不影响什么)。性能我觉得至少要比均匀分配好吧,但如果某个区域人流量比较大,该区域的顾客大部分都被分配到等待这个电梯,可能会出现其他电梯都闲置,这个电梯还在勤勤恳恳俯首甘为孺子牛的现象。
-
【算法】寻找最短到达时间+动态调整
(感觉挖的坑越来越深,埋下的bug越来越多)
前面那个算法在客流量大的时候会实现不同电梯专门跑短程的效果,等到客流量减少(或者一开始人就不多),性能就会下降。因此可以设置如果出现电梯没事干了可以发出信号,申请重新排队(俗称:没事找事)。只要没有电梯独自歇着,大家都动起来,人人都献出一份爱,世界就会有美好的明~~天~~🎵
具体实现需要设置一个通知信号,一旦一个电梯空闲就发出信号,输入线程被唤醒,采集各个电梯的等待队列,重新执行分配。
优:听起来没啥毛病是吗
缺:难,而且真的不一定会快
纸上谈兵:挺好,但 是 (“但是”从不缺席)前提是你优雅地处理好线程问题(重新分配是个大坑)。细节很多,随之bug也会很多。如果个人能力不足(”个人“说的就是“我”)牺牲正确率去追求性能是得不偿失的。
最终我选了倒数第二个——寻找最短时间,因为动态分配感觉真的麻烦,且意义不大!
由于我个人是迭代设计,所以第二次基本上没什么代码量,也没什么bug,于是在写的时候利用中测信息测试了一下不同代码的效率
-
第一次:分配算法胡写,图片中的一四情况只记录楼层差,二三情况记录两倍的楼层差
22.6638 13.2962 15.713 23.8505 21.6484 37.3491 55.6518 50.8273 47.8613 45.6267 -
第二次:如算法预期,但如果电梯都满员就直接分给A
22.6358 ↓ 13.3048 ↑ 15.7009 ↓ 24.635 ↓ 21.6082 ↓ 34.6433 ↓ 50.783 ↓ 45.8634 ↓ 45.8699 ↓ 45.4975 ↓可以看到整体都快,数据越强快的越明显
-
第三次:观察到因为只统计电梯中人数,会使一开始电梯A没装人但等待队列很长还往A里分配,故评测指标加入等待队列长度。
这些数据都没有加锁,因为是评估算法,数据有出入问题不大。总体上还是在降,但是数据弱的时候多台电梯跑,反而没有一台跑的快。最终用的就是这版。
22.6422 ↓ ↑ 13.3037 ↑ ↓ 15.668 ↓ ↓ 24.6211 ↓ ↓ 21.643 ↓ ↑ 34.1253 ↓ ↓ 50.7593 ↓ ↓ 44.7377 ↓ ↓ 43.4462 ↓ ↓ 39.6217 ↓ ↓
SOLID分析:
-
Single Responsibility Principle
电梯类在这方面表现较差,除了与电梯运行相关,电梯类还负责了电梯内人员信息的计算以及换乘楼层的计算。
或许电梯类应该改进为“封装了内部等待队列类且实现运行接口的线程类”
-
Open / Closed Principle
实现不佳,虽然给各个类进行了分类打包,但是依然是全都使用public可见,不同包间的数据随意访问,没有很好的封装。
但是感觉目前我的代码还过于简单,迭代次数也有限,开闭原则存在感不高。
-
Liskov Substitution Principle
这次只有电梯采用了”继承“,还专门建立了”工厂模式“,自认为这点实现的还可以
-
Dependency Inversion Principle
第三次作业没有用到“抽象”思想,下次会注意,养成良好的面向对象编程习惯
-
Interface Segregation Principle
这个原则给我一定的启发,之前没有采用接口就是觉得多个类对接口的不同需求会导致接口显示的成员数量很多,与不使用接口无异。原来可以设置多个小接口!
基于度量的程序分析🔧
- 第一次


- 第二次


- 第三次


如上一个模块分析的,电梯类过于繁冗,应该改进
输入线程做了很多Main应该做的事,所以复杂度较高。但是我乘客线程想要判断乘坐哪个电梯必须获得所有电梯、调度、队列的信息,只有输入能完成传递的任务。所以根本上我设计的耦合度太高,还是不够面向对象。可以考虑封装全部电梯信息,并只给乘客展示其所需内容,可以一定程度上解耦。
下面是协作图,限于篇幅只展现了一个乘客不换乘的情况,其余情况循环执行即可

多线程的心得🧡
第一次作业的调度逻辑其实很简单,但是让作为初学者的本最人困惑的就是为什么写多线程多线程到底该怎么写,怎么调试,怎么测试。
-
为什么写多线程
初识多线程,我在想几个问题:为什么我们这次作业被安排在多线程单元下?这次作业是否可以用单线程实现?如果可以,二者实现方式有何优缺点?
问题的关键在于乘客是实时输入的,如果只是简单的输入乘客后请求输出最优解,那这就完全是一道算法题,而且是逻辑并不复杂的算法题。我们甚至可以无脑搞一堆算法都试一遍(时间允许的话),然后输出时间最短的那个(后来发现竟然真的有人这么干,在下佩服)。
但是...如果后总是有但是,但是这里是实时的,我们不可能等到所有客户都提交完请求后再慢慢悠悠启动电梯(顾客把你程序员头给打掉),我们一定是一边处理请求一遍接受新的请求,即所谓并发。
那么通常来说,我们什么时候要考虑采用多线程设计呢?
-
高并发
即我们这次作业的特点,你无法预知有多少个用户并发产生请求,你必须本着顾客就是上帝的原则对他们一视同仁
-
后台处理大量任务
举一个我刻骨铭心的小学奥数的例子(我仍然记得当时有一道题是早上起来安排穿衣服、洗漱、烧水、做早饭等的问题,同学们开心的抬杠起床后也可以不穿衣服就去刷牙)
小明家来客人了,要招待客人喝茶。他算了一下洗水壶要1min,烧开水min,洗茶壶1min,洗茶杯1min,拿茶叶2min。为了使客人尽早喝到茶,聪明的你能帮小明设计一下怎样安排用时最少吗?最少用时是多少?
答:请小明用完水壶、茶壶、茶杯就立即洗掉不要等到客人来了再洗这题可以吐槽的点太多,我们按下不表(?掉什么书袋)我还是想感慨一下小学奥数简直就是编程思想入门!其实策略都是共通的,那么聪明的你肯定知道先烧水,让它先慢慢烧着去,我们还可以同时干别的事。也就是把一些任务丢给后台去完成。
我们看这道题给的原文解析:
水壶不洗,不能烧开水,但烧开水的时间可以同时做xxxx,所以最少16分钟
合理安排实践总结:
- 要做哪些工作?
- 弄清工作的顺序,先做什么,后做什么?
- 哪些可以同时做?
这最后三点其实就是我们写多线程的要义,虽然这很弱智很显然,但是还是想再次感慨策略真是一个经久不衰、老少皆宜的话题,反映了自然界生物本性就是在永不停歇地追求利益最大化(理性经济人?跑了跑了)
-
时间紧、任务重
人多力量大嘛,内容有重复,不再赘述
经过以上(弱智的)分析,谁都知道我们这单元作业为什么要用多线程了。
-
-
怎么写
其实老师在三次授课中讲的已经很细致,在此梳理一下我认为和作业相关的点
-
多线程的实现
方案1:继承Thread类
方案2:实现Runnable接口【推荐】
使用Runnable接口在这单元设计中有两个直接的好处:
- 一步设置线程名字,方便调试。而Thread设置名字还需要单独的一步
setName() - Java是单继承语言,方便继承。在第三次作业中出现A、B、C三类电梯,继承和多态很方便!
- 一步设置线程名字,方便调试。而Thread设置名字还需要单独的一步
-
暴力轮询与CTLE
这一单元首次见识到了魔鬼的CTLE。
出现这种情况要么是没有熟悉多线程的sleep()和notify()的意义而暴力轮询
要么是在程序中循环的部位错判误判致使陷入轮询或死循环
而我本人也遇到了一种玄学CTLE(说玄学本质是我太菜不懂原理),其原因应该是我采用了不好的设计,在一个线程中操控了另一个线程。在调试过程中我发现在“被操控”的线程sleep()时,“操控者”的CPU占用率极高。
-
遵循SOLID原则
具体到此次作业要注意在共享数据(等待队列)中做好线程安全保护,其他线程放心使用
run方法中是一个线程的全局把控,正如普通类里的main方法,不宜面向过程太过繁琐,我在第三次作业中因为偷懒没有好好设计直接上手,导致run方法十分冗长。
如何解耦我依然没有学会,做设计、迭代的时候脑子总不往这方面想。
-
atomic包
如果read-modify-write 和 check-then-act计算只涉及一个变量,在我们的作业中如统计队列人数的number变量,就可以使用atmoic包提供的原子变量。
操作十分简单!!
-
读写锁
遇到一写多读的情景,如果再一味的用”互斥“的逻辑有些浪费,使用操作难度稍微高一丢丢的读写锁很不错。
因为是锁,所以写锁unlock后也具有唤醒能力,而原子操作没有这个功能哦。所以如果想要让沉睡的线程因为状态改变而被唤醒,同时保证线程安全可以尝试读写锁。
-
线程安全容器
这次等待队列使用LinkedBlockingQueue很香,入门零难度,直接拿来用就好了。
-
死锁问题
由于二、三次作业都迭代采用了第一次的设计框架,使用了一个调度对应一个电梯的一对一模式,不会出现资源竞争问题,从而没有遇到死锁问题。
-
-
怎么调试
都说多线程因为难以复现所以简单调试没用,于是初入门的我一开始吓得以为多线程不能调试。后来发现多线程调试没有那么神奇,大部分错误我们还是可以通过老方法搞定的(事实上我好像没有遇到过什么不能复现的bug)
以下记录一些可行的调试方法,以后想用可以直——接——查——
-
printf大法,我没用过不知道怎么用好用(只有在OS的恶劣环境下我才愿意printf)
-
断点调试
调试时只需要对原来的红色断点【右键】、查看【suspend】选项,如果设置为【all】,就会出现第一个线程启动后遇到断点所有线程直接阻塞;如果设置为【Thread】,那么不同的线程可以并发到这个断点。
IDEA调试界面有【Frame】和【Variables】两栏,【Frame】下可选则要观察的线程。这时乖乖给线程命名就很好挑线程啦
表格来自马安玲同学的研讨课分享
按钮 说明&快捷键 蓝色向右下箭头 单步调试:执行一条语句,但是遇到方法调用时不进入,直到方法执行完成后直接继续。(F8) 蓝色向下箭头 单步调试:执行一条语句,但是遇到系统类库方法调用时不进入,直到方法执行完成后直接继续。如果遇到非系统类库中的方法,则会进入该方法进行调试(F7) 红色向下箭头 单步调试:执行一条语句,遇到方法调用时会进入方法进行调试(Alt+Shift+F7) 蓝色向上箭头 从步入的方法内退出到方法调用处,此时方法已执行完毕,只是还没有完成赋值。(Shift+F8) 蓝色向右下小箭头+竖线 运行到光标处(Alt+F9) -
JProfiler
与此同时还有用了都说好的【JProfiler】,配合IDEA的插件可以用【CPU views】分析CTLE起因,用【Thread】观看线程运行状况排查死锁,【Monitors & locks】可以实时查看锁情况。如果乖乖给线程命名了就超好用啦。

-
JConsoler 没用过
-
jstack 同上
-
-
怎么测试
我这次好懒,第二次才磨磨蹭蹭写了评测姬,写完了也没充分测试。大佬们分享了很多评测姬技法,还有如何查RTLE和CTLE,我懒了...
记录三句能实时投放的神奇咒语🧙♀️
# python # 引号中前者是通过time.sleep(random.random())设置好了投放时间的数据生成器 # 引号中后者是我打包好的jar包 import subprocess p = subprocess.Popen('python E:\\Python\\test\\creatData.py | java -jar E:\\Java\\hack\\jars\\HW3.jar', shell=True) p.wait()这个语句的具体参数如下:
class subprocess.Popen( args, #shell = True 时如果arg是个字符串,就使用shell来解释执行该字符串 bufsize=0, executable=None, stdin=None, stdout=None, stderr=None, preexec_fn=None, close_fds=False, shell=False, cwd=None, env=None, universal_newlines=False, startupinfo=None, creationflags=0) -
jar包
好处有三:
- 这单元作业存在官方包,互测用命令行编译运行会出现麻烦的事情,不如直接打个包。
- 据助教姐姐所说,在IDEA的环境下直接跑程序会“规避”一些并发问题,打个包用命令行可能会更容易暴露问题
- 互测时把每个人的jar包命名为saber.jar、lancer.jar什么的放在一起再跑评测姬简直不能再方便!(是不是大佬早都这么干了...)
我每次都忘怎么打包,IDEA很方便特此在这记录
【File】-> 【Project Structure】->【Artifacts】->【+】->【JAR】->【From ...】

做三处更改,【OK】->【Apply】->【OK】

【Build】->【Build Artifacts...】->【Build】即可,即可在"\out\artifacts\*_jar\"里找到包啦

bug与泪💧
三次作业强测互测最后发现了1个bug,就是最后一次强测,都是我懒得跑评测姬的锅,慢慢评测姬写好了却懒得跑有什么办法呢。
【FROM3-TO-4】换乘时我应该设置C类电梯的中转站为5楼而不是3楼。修这个bug就在原先那行加个三目运算符问号即可(git算是两行),但我超超超超心痛。强测里两个点有从3到4的数据,于是挂了两个点,分数直接89😥。其他点的分数全体98+,但有什么用呢...
线程安全方面自认为应该没什么问题,第一二次互测也没被大佬揪住(第三次一看同屋代码就知道是C屋了),这个功能bug我但凡跑一下评测姬绝对能跑出来...(后悔 * +∞)
其实从第一次到第三次中间经历了一个清明节,前后心态发生了巨大的变化。之前如上个单元博客所写,我的状态是过于焦虑过于小心,把代码翻来覆去的重复测,而这个单元直接变为不想写、不想测。
清明时读《当尼采哭泣》,那几天常常熬到半夜,其中关于心理治疗、精神分析、人生意义、如何面对死亡与虚无等话题搞得我几天内都处于精神恍惚的状态。我记得一天晚上我看到3:30,之后醒来两三次,迷迷糊糊做的梦都是密密麻麻排列的”心理治疗“、”催眠“之类的字眼。第二天起来,或许由于睡眠不足,脑子就不怎么转的动,干脆也不写作业了,一口气把书看完了。其实虚无一直是萦绕在我心头的幽灵,而心理学与认知科学又是我心目中的处女圣地,这也是为什么这本书如此和我的胃口。今年过年的时候我迎来了20岁生日,我翻出以前所有的日记、留下的小物件,试图去自行梳理出一个关于这类问题的“答案”来,但是除了无限的回忆外什么也没有找到,我发现回忆过去对未来的指导意义始终有限。但我终于遇到了这本书,这本书给出的“答案”以及给出答案的方式都是我前所未见的。到现在我还没写完整出的书评,因为我认为这是个大工程,一定要再通读一遍细细的梳理才不算愧对它。
回到bug。前一阵子状态很不好,各种因素交织在一起,使我感到整个人处于精神和生理双重瘫痪的状态。直到4.15号,我的心情都很不好很不好,我晚上躺在床上会萌生奇怪的念头,我感觉我的生活中完全没有一丝丝力量感可言,有没有人能打我一顿呢,或许只有拳头对我还有力量可言。这段时间的作业产出效率也十分低,因此才有了上面那个但凡测一测就能查出的bug。
但转过头来一想也没那么耿耿于怀了,孕育这个bug的是错综复杂的各方面因素,这个过程中我也在思考我也在成长(虽然是其他方面而不是代码方面hhh),一味地埋怨自己傻...真傻...太傻...属实有些片面了,至少这个过程中体验了一把被抽去力量以及恢复的过程,各中微妙的细节以及感悟都很有意义。
最后感谢神曲《新宝岛》,在单曲循环了2天后我感觉整个人浑身又充满了来自猛男の力量【手动滑稽】
以上。(为什么我博客总写这么长,我不想码代码了,让我去码字好了,做个沙雕营销号小编挺好【手动狗头】)

浙公网安备 33010602011771号