• 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

第二单元总结

对于第二单元的作业,由于我一开始在架构设计上花了很多时间,而且很多地方考虑到后续作业中可能需要扩展的地方,所以我的三个作业整体架构几乎没有变化。

总体上采用生产者和消费者的模式,输入类,调度器类电梯类继承线程,GeneralQueueWaitingQueue作为托盘线程安全。其中,线程间共享对象的增减是为了保证线程安全而实现的,这样在线程类中,就不需要过多考虑共享对象的操作可能带来的线程安全问题,这样问题就可以“分而治之”了。用荣老师的话来说就是“铁路警察管一段”,可以在一定程度上降低问题的复杂程度。

第五次作业

n5v5w5f2hyh3815.png

我来分享一下我整个架构的设计思路。

先分析三个线程类:

首先,为了降低程序的耦合度,工程上一般把输入作为一个单独的类来处理。原因是它可以与内部核心功能分离,当输入需求发生变化时,可以保证内部核心模块不受影响。因此,有必要设计一个InputHandler类来单独处理输入。

第五个操作最简单。刚开始的时候A、B、C、D、E各有一部垂直电梯,保证不会再有电梯。此外,所有请求都是为了改变同一栋大楼的楼层。因此,当从输入线程,获得乘客请求时,该请求实际上默认属于某个电梯(例如,该请求是从B区的第二层到第五层,并且该请求注定在属于B区的第二电梯中完成)。但是,为了遵守“分而治之”的原则,在将输入的请求进行甄别并分配到对应的电梯的工作需要由一个调度器单独完成。设计了一个Dispatcher类.

因为电梯要不停的运行,所以要设置成线程类,所以这个不讨论。

重新分析两个线程安全类:

在第五个任务中,因为我预感到可能会有一个传输请求,所以在输入类和调度程序之间添加了一个GeneralQueue来存储从输入类获得的所有请求。这样以后如果有转移请求,可以作为通用请求直接加载到GeneralQueue中,兼容性很大。

在调度器线程和电梯线程之间,为每一个电梯设计一个WaitingQueue,在调度器的分配下,保证WaitingQueue中的每一个请求都被其对应的电梯完成。这里之所以不将这些请求直接加载到相应的电梯中,主要是为了解决超载的情况,更好地模拟现实生活中电梯的实际运行情况。我在电梯类中设置了一个私有的insideQueue来指示已经上电梯的人,所以在这种情况下,如果一部电梯中insideQueue的大小没有达到最大容量,也就是电梯没有超载,可以直接从WaitingQueue中获取自己的等待请求;如果过载,您可以在从WaitingQueue获取请求之前等待电梯中的请求完成。

同步块的设置和锁的选择

我在两个线程安全方法中都添加了synchronized。起初,我认为这是一个很好的设计模式,因为它可以确保线程安全。但是不好知道,三次作业下来,听老师说其实很差。最好不要在函数中加入synchronized,这样锁的对象就是这个,这样锁力就很强,真正保证了线程安全。不过还是建议使用synchronized(obj),找一个大小合适的锁来锁,synchronized中的代码尽量简洁。这一切都是因为锁定很贵,所以成本很高,性能也会受到影响。

影响。“杀鸡焉用宰牛刀”,我们所需要做的不仅是保证程序的正确性,还应该尽可能追求高的性能。

调度器设计

调度器主要是通过调用generalQueue的receivePersonRequest方法原子性地获取一个请求,,然后调用其自身的分配调度方法dispatchRequest()将这个请求分配到对应电梯的等待队列waitingQueue中。

这里对于分配到对应电梯的策略基本上遵从基准策略

  • 在第五次作业中,只有纵向电梯,判断请求属于哪一楼座,然后如果该楼座只有一个电梯加入该电梯的等待队列即可,如果该楼座有多部等待电梯,由于他们的优先级一致,所以选择循环遍历给予每个电梯请求,新的请求加入容器中下一个电梯的等待队列中。

  • 第六次作业,加入了横向电梯,由于保证请求要么同楼座要么同楼层,只需要在调度器中加入一个getRequestType()方法判断该请求属于纵向还是横向请求,然后根据其类别再调用buildingDispatch()或floorDispatch()方法即可,这俩方法对请求的调度同第五次作业的描述。

  • 第七次作业,加入了换乘类请求的可能,(即请求的起始楼层和终点楼层与其实楼座和终点楼座同时不同),为了兼容这种情况,在getRequestType()的请求类型中除了纵向、横向类型还有换乘类型,同时dispatchRequest()如果判断是换乘类型就会调用换乘方法将请求拆分为横向、纵向请求再调用相应类别的分配方法。

线程协作

igcckeuwl2v3816.png

 

如上图所示即为我设计的三个线程之间的协作图,其中绿色箭头是为了满足第七次作业换乘的需求而增加的电梯线程向GeneralQueue装入需要换乘的人的下一个阶段的请求(具体细节在后面第七次作业处说明)。

第六次作业

1m2eovzjf3u3817.png

 

第六次作业相比于第五次作业,主要有两个变动:1、可以通过输入增加电梯 2、电梯种类增加了横向电梯,可以完成同层不同楼座的请求。

对于通过输入增加电梯,只需要修改在第五次作业中的InputHandler类即可,这里充分体现出把输入单独作为一个类的好处

这里由于我设计架构的可扩展性较强,这里只需要把第五次作业的电梯类内部不改变更名为纵向电梯,然后再加入一个横向电梯类,

第七次作业

slry4xrntdy3818.png

 

第七次作业相比第六次作业,主要有两个变动:1、新增电梯定制化功能 2、请求的起始楼座、起始楼层和目的楼座、目的楼层可以同时不同,即需要换乘。

同样,由于在第五次作业的时候就考虑到了后面可能会涉及到换乘的问题,所以设计的架构能够很轻松地支持加入换乘。

这里换乘的实现主要依赖于GeneralQueue实现单例模式、增加调度器对换乘类请求的识别并实现将换乘请求拆分、换乘类请求拆分后的某段请求完成后再将下一段请求装入GeneralQueue(单例模式的作用在此体现)

由于整体架构几乎没有改变,给出第七次作业的类图如下:

lhdbitqbhah3819.png

 

分析bug

自己的bug

第五次作业

  • bug

    • 这一次作业强测没有出错,但是在互测时被hack到,原因是没有保护官方包的输出线程,导致输出被hack,时间戳不能满足递增

  • 修复办法

    • 为输出单独创建一个OutputThread(),通过静态方法加锁实现输出的同步

第六次作业

这一次作业强测互测均没有出现bug

第七次作业

  • bug

    • 这一次作业强测互测都源于一个bug:对于起始楼层和目的楼层一样的请求,直接判为了不需要换乘的横向请求而忽略了可能该楼层的横向电梯不能满足在请求的起始楼座和目的楼座都停靠

  • 修复办法

    • 在判断请求类型的时候,对于起始楼层和目的楼层一样的情况,遍历对应楼层的所有横向电梯,如果该楼层有电梯能在请求的起始楼座和目的楼座都停靠,则判定该请求为横向请求;如果没有,则判断为换乘请求,这样的话就可以到别的楼层换乘,拆分为三段请求(1:在起始楼座,起始楼层->换乘楼层;2:在换乘楼层,起始楼座->目的楼座;3:在目的楼座,换乘楼座层>目的楼层),再调用相应的横向/纵向调度方法即可。庆幸于自己设计的架构很清晰,所以只需要改这里类别判断,别的方法并不受影响,并且直接调用就能满足要求

别人的bug

经过这三次作业,大家的bug主要集中于:

  • 保护官方包的输出线程

  • 没有考虑电梯超载的情况

  • 线程安全没有保证,当同一时间戳输入多个请求的时候出现问题

  • 第七次作业中换乘只考虑了换乘请求拆分为三段请求的情况,没有考虑换乘请求也可能只用拆分为两段

这一单元的测试确实与第一单元都很大不同。第一单元一个输入就会得到一个输出,并且每次的结果都不会改变,容易复现,并且容易debug找到问题出现的地方。而这一单元由于这是多线程协同,很多情况不能复现,debug也比较困难。

我找bug几乎是采用黑盒模式,把输入的极端情况(比如同时多个请求、超载、1楼10楼这种边界位置的转向)考虑到以后测试程序结果是否如逾期,知道这样hack确实不太好,但是确实挺有效的。

心得体会

这一单元其实总体来说感觉完成题目需求比第一单元要简单,所以感觉花费的时间也要少很多。而且第五次作业好的设计架构真的会让后面的迭代开发容易很多,几乎只用根据需求微调一下具体实现细节就可以,让我更加认识到设计的重要性。

但是完成需求,想要真正领悟到多线程协同工作,以及同步块的设置和锁的选择却比较难。比如我在最后三次作业做完后老师上课提到最好不要在函数上加synchronized,这样锁的对象是this,这样锁的力度很大,确实很好地保证了线程安全,开销很大,性能会受到影响。“杀鸡焉用宰牛刀”,我们所需要做的不仅是保证程序的正确性,还应该尽可能追求高的性能。推荐使用synchronized(obj),找合适大小的锁去锁,并且synchronized里的代码尽量精简。这样的话就会需要很明确地知道同步的具体对象是什么。以后我再遇到多线程相关问题的时候我一定会采用这种方法。

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