• Welcome to the world's largest Chinese hacker forum

    Welcome to the world's largest Chinese hacker forum, our forum registration is open! You can now register for technical communication with us, this is a free and open to the world of the BBS, we founded the purpose for the study of network security, please don't release business of black/grey, or on the BBS posts, to seek help hacker if violations, we will permanently frozen your IP and account, thank you for your cooperation. Hacker attack and defense cracking or network Security

    business please click here: Creation Security  From CNHACKTEAM

Recommended Posts

第二单元博客

1 同步块的设置和锁的选择

1.1 锁的选择

单元2的第一课讲了同步锁的方法,下一课讲了ReentrantLock高级锁。虽然ReentrantLock可以实现更多的线程控制功能,但是考虑到用同步代码实现相对容易,不容易出错,所以在三次迭代中只使用同步方法来锁定共享对象中的方法。

1.2 同步块的设置

在这三个作业中,我设置了请求队列类PersonQueue。在第五次作业中,这个班级是所有班级中唯一的共享班级。所以在第五次赋值开始对synchronized的理解不到位的时候,PersonQueue类中的所有方法都是synchronized,在接下来的两次赋值中沿袭了传统。

在前两次作业中,我将PersonQueue封装成两个类,VerticalTrack和HorizonTrack,其中存储了PersonQueue数组,用于电梯轨道管理。所以,在新增加的两个track类中,我也无意识地同步了其中的所有方法。

另外,有时候电梯会在同一个条件判断语句中调用多个共享方法。为了保持语句块前后共享对象的状态一致,需要同步电梯线程run()方法中的语句。

比如下面电梯线程run()方法开头的代码,如果没有synchronized同步,在一个if()括号内调用verticalTrack.isEmpty()和verticalTrack.isWaitEmpty()时,共享对象verticalTrack的状态可能会发生变化,这将导致逻辑表达式的意外结果。另外,为了保证两个if()语句块中verticalTrack.isEmpty()的返回值相同,我把两个条件语句放在同一个同步语句块中。

同步(垂直跟踪){

//进程结束

if(vertical track . isempty()vertical track . iswaitempty()

inelevator . isempty()new main building . getinstance()。isEnd()) {

返回;

}

//等待下一个请求

if(vertical track . isempty()inelevator . isempty()){

尝试{

vertical track . wait();

} catch (InterruptedException e) {

e . printstacktrace();

}

继续;

}

}

2 调度器与线程交互方式

在三次作业中,我实际上没有实现真正的调度程序。最多是将电梯请求按照出发楼层和楼层分配到特定队列中,没有统一的类来组织各种电梯接送行为。

在第五个作业中,我没有使用调度器,因为它不涉及电梯线程之间的交互和影响。在第六和第七个操作中,虽然引入了多部电梯共用楼层和水平电梯的概念,但我们知道,在前几年。

博客后发现使用调度器电梯自由竞争相比性能差异并不大,并且实现电梯自由竞争从难度上明显小于实现调度器,因此我在后续两次作业中仍然采用电梯自由竞争的策略。

  而对于线程之间的交互,我在三次作业中均采用了生产者-消费者模式,电梯间的交互通过读写共享对象(此处为请求队列)实现。输入线程作为生产者将请求放入请求队列(托盘)中,而电梯线程作为唯一消费者每次从托盘中取走相应的请求,进行开关门上下客的处理。而在第七次作业中,由于引入了换乘,一部分的电梯请求也作为生产者,将换乘后的电梯请求添加到相应请求队列中。

3 架构模式分析

3.1 第五次作业

  类的调用关系图如下。第五次作业中设置了三个线程类InputThreadDispatcherElevator实现电梯请求的分配和处理,它们均在主函数类中创建并运行。而共享队列PersonQueue也在主函数中被创建,并且提供线程之间交互的渠道。
  此外我还专门设置了线程安全的输出类Wrapper负责输出,防止输出序列不递增的现象发生。

  而在电梯接送策略方面,我使用了生活中绝大多数电梯使用的look策略。但是由于我在这次作业中将每一座的所有请求放入同一个队列中,因此每次寻找最高楼层和最低楼层的请求时需要同步遍历整个队列,而且有时还要从队列中取出特定方向特定出发楼层的请求,需要传入很多参数,导致代码块设置十分混乱。因此在第六次作业中我对请求队列设置的逻辑进行了重构,对每个请求进行了精细化的分配,解决代码风格杂乱、容易出错的问题(详见下一次作业的分析)。

j0iqg2aef1r4293.png

  UML协作图如下。其实这次作业中的协作图是三次作业中最冗杂的,我设置了三级流水的线程InputThreadDispatcherElevator分别负责电梯请求的输入、分派、处理。但是后来发现其实电梯的输入和分派完全可以放置到同一个类中进行处理,而标志输入是否结束的isEnd信号其实可以全局共用同一个。因此第六次作业我只设置了InputThreadElevator两个线程类,更清晰简洁地处理了电梯分配的过程。

xnuv0zkvjwa4294.png

 

  第五次我的代码架构真的很shi,出了不少bug,因此我在第六次作业时下定决心对我写的shi山进行大换shi。

3.2 第六次作业

  第六次作业我将线程类简化成了InputThreadElevator,并且使用了单例模式创建了NewMainBuilding类,集中管理整个新主楼的电梯等待队列。
  初始电梯线程在NewMainBuilding类初始化时创建,而新加入的电梯线程则在输入线程InputThread创建。此外将输出线程安全类改名成了Printer

  吸取上一次作业的教训,我为特定楼座、特定楼层、特定方向的请求单独设置队列,将其精细化处理。具体来说,我在单例类中设置了轨道类VerticalTrack/HorizonTrack数组,大小分别为5/10,表示特定楼座/楼层的轨道;而每个轨道类内又设置了请求队列PersonQueue的数组,大小分别为10/5,表示给定楼座/楼层下从某一楼层/楼座 出发的请求队列;而每个PersonQueue又由两个ArrayList<PersonRequest>的列表组成,分别储存从同一地点出发,但是方向不同的两种乘梯请求。
  这样一来,只需在NewMainBuilding -> Track -> PersonQueue逐级实现getRequestaddRequest方法。则可以从InputThread调用NewMainBuildingaddRequest方法实现请求的精细化分配,而且在获得请求时,这次作业将从队列中查找请求的过程转化为了通过出发信息检索队列的过程 ,除去了许多代码冗长的方法,提升了面向对象编程的风格特性。

  关于横向电梯,我采用了类似环形的look方法,定义了换向逻辑如下:
  电梯换向条件(大前提,关系):1.电梯是空的(注意是下客后为空)。2.上电梯的乘客中没有捎带者。3.当前轨道有请求。
  此外再满足接下来两者中的一者(关系),则电梯更换换方向:1.当前楼层有逆向的请求。2.原方向上没有请求。

djetifivswc4295.png

 

  至于时序图,由于删除了Dispatcher类,因此线程的时间关系较上一次作业更为清晰。
  这次作业中在只在单例类NewMainBuilidng中设置了输入结束标记isEnd,当输入完毕后在将其设置为true,并且唤醒所有的电梯线程。电梯线程检测到当且的轨道为空、电梯内乘客为空并且输入结束标记isEnd为真后,结束run()方法。

eaybqjfxylq4296.png

 

3.3 第七次作业

  第七次作业主要有三个任务。

  第一个是自定义电梯参数,这个只需在电梯类内增加相应字段,简单替换方法中的参数即可。

  第二个是实现电梯的换乘,本次作业中我采用了链表的方法,将官方包的类PersonRequest封装至自定义类MyRequest中,并且在其中设置字段nextRequest链接到下一乘梯请求。在输入乘梯请求时,在InputThread中根据标准换乘策略,将请求拆分成1~3个同座或同楼层的可达请求,并且将其通过字段nextRequest链接起来。
  而在PersonQueue中,新增了一个名为waitRequestsArrayList用于存放前序请求尚未执行完的电梯请求。在前序请求完成后,只需将后序请求从其对应PersonQueue中的waitRequests移动到同一个类中待处理的请求列表即可实现换乘。

  第三个是开门权限位switchInfo的设置与使用,其实这里只需要对水平轨道类中getRequest方法进行修改。在获取乘梯请求时将switchInfo信息传入,在遍历数组时将无法开门的楼座通过continue语句跳过即可。
  此外这里还有一个问题,就是对于不同的switchInfo,相同起点相同方向的乘梯请求可能会有不一样的上梯行为。如对于在AB座又开门权限的电梯,请求A->B能上电梯但是请求A->C不能上,但是按照之前的分配逻辑这两个请求会放在同一个ArrayList中,因此直接在队列中使用getOneRequest的模式会出锅。
  为解决这个问题,我首先想到了在队列中写遍历方法,但感觉这样代码会很杂乱,可能还会涉及到垂直电梯的更改,pass!最后我再次采用精细化分配的思想解决了这个问题:我在横向轨道类HorizonTrack中将PersonQueue[5]变为二维数组PersonQueue[5][5],前一个下标代表出发楼座,后一个下标代表抵达楼座,将相同起点、相同方向、但不同终点的请求放入不同的队列中,这样以来只需对HorizonTrack中的方法调用数组的地方进行一位至二维的转化即可。

tvksosa03eu4297.png

 

  时序图和上次比较,电梯线程自身也成为了生产者。当一个乘梯请求结束并且下一个乘梯请求存在时,需要激活后续请求并且唤醒轨道上的所有电梯。

dodfg5afgmw4298.png

 

4 程序bug分析与测试策略

4.1 自己的bug及解决方法

bug可真多啊~

4.1.1 第五次作业

  这次作业没做课下测试,中测过了之后就想着清明节玩去了。回来一看强测WA声一片,再仔细看看自己代(shi)码(shan),一度怀疑前几天自己的眼睛是不是瞎了。“是可AC,熟不可AC”,找到bug之后自己都给气笑了

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now