• 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

一、同步块的设置

在第二单元的作业中,我只使用了同步块的设置,没有使用读写锁,所以我只从同步块方面介绍了我的三个作业的设计。从第一次作业开始,我就开始了多线程,对synchronized的困惑,看wait/notify,到第三次作业,我已经搞清楚了这些关键字和内置方法的机制,能够直观的分析轮询bug,中间有多少困惑,痛苦,顿悟,喜悦。

第一次作业

刚写的时候真的什么都不懂,只能照着电脑上的代码写。当我开始时,我首先实现了一个RequestQueue类,然后在几乎每个方法中添加了synchronized和notifyAll。真是战战兢兢,如履薄冰。

具体来说,该类中使用了以下函数:

public synchronized void add request(person request请求);

public synchronized person request getOneRequest();

public synchronized void setEnd(布尔isEnd);

公共同步布尔isEnd();

public synchronized boolean isEmpty();

好在数据量不大,程序的主要时间是模拟电梯的运动和门的开关,所以性能损失不大。在第一个任务中,每栋建筑中只有一部电梯,所以我为每部电梯分别创建了一个RequestQueue类型的对象。通过这样做,我无意中避免了太多的notifyAll错误。当然第二次作业就暴露了。

第二次作业

在第二次作业中,我学会了使用同步块,代码有了很大的变化,体现在同步块的设置和notfyAll语句的减少。

首先,我删除了RequestQueue类中的getOneRequest()函数,并在Elevator类中的personIn方法和directionUpdate方法中设置了访问公共消息队列的同步块。

同步(进程队列){.}

这样做有两个好处:

你可以在同步块中写任何你想写的代码,这样在设计RequestQueue类的时候就不用考虑电梯类的行为,非常灵活。

电梯定义如何访问消息队列比消息队列本身定义被访问的函数更直观。

其次,我在测试过程中发现,如果多个电梯共享一个消息队列,过多调用notifyAll会导致线程频繁唤醒,从而导致CTLE。所以我开始减少notifyAll的使用。实际上,notifyAll只需要在两种情况下使用:消息队列添加了新的元素,或者消息队列输入结束。.因此,您只需要将notifyAll添加到以下两个函数中。

public synchronized void add request(person request请求);

public synchronized void setEnd(布尔isEnd);

第三次作业

第三个作业中同步块的使用和第二个作业没有太大区别,除了多了一个RequestCounter类,也是模仿计算机内容写的。

RequestCounter采用singleton模式,一种私有化的构造函数,依靠getInstance方法返回同一个RequestCounter对象。并通过release方法提交作业,通过acquire方法接受作业。这两个方法是用synchronized修饰的。

因为我在第三次作业中使用了动态拆分请求的方法,所以通过使用RequestCounter来管理电梯线程何时结束非常方便。

二、调度器设计

因为我采用了三个作业自由竞争的策略,所以调度器的功能很简单,直接合并到输入线程中。所以我就直接介绍input thread的实现,以及如何与各个线程交互。

在输入线程的构造函数中,为每个楼层和楼层建立一个消息队列,并初始化前六部电梯。在初始化过程中,电梯将被绑定到消息队列。

this . wait queues=new ArrayList();

for(int I=0;i5;i ) {

request queue wait queue=new request queue();

电梯电梯=新电梯(i 1,(char) ('A' i),8,0.6,wait queue);

电梯. start();

waitQ

ueues.add(waitQueue); } for (int i = 0; i < 10; i++) { RequestQueue waitQueue = new RequestQueue(); this.waitQueues.add(waitQueue); } Ring ring = new Ring(6,1,8,0.6,31,waitQueues.get(5)); ring.start();

之后,在run方法中,调用官方输入包获取请求。如果是乘客请求,就会把请求加入消息队列;如果是电梯请求,就初始化电梯并与对应的消息队列绑定。

在官方输入包结束之后,调用上文提到的RequestCounter类的acquire方法验收,然后结束所有消息队列,消息队列的一旦结束,相应的电梯在运载完当前电梯内的乘客后,也会相应结束,整个程序就彻底结束了。

public void run() {
    while (true) {
        if (request == null) {
            break;
        }
        //TODO
    }
    for (int i = 0; i < requestNum; i++) {
    	RequestCounter.getInstance().acquire();
    }
    for (RequestQueue queue : waitQueues) {
    	queue.setEnd(true);
    }
}

三、线程协同的架构

第一次作业

第一次作业的线程协同架构采用了生产者——消费者模型,其中生产者是输入线程Input,消费者是电梯线程Elevator,它们之间的桥梁是RequestQueue类的对象。每当Input得到请求后,就会调用RequestQueue的addRequest方法,向消息队列添加请求。而每次Elevator被唤醒(抵达新的楼层或者消息队列更新)后,都会去检查消息队列,查看是否能够捎带队列中的请求。

除了上述三个继承了Thread的类,还有Main类和Output类。Main类主要负责初始化Input、RequestQueue和Elevator,并调用线程的start方法。Output类对于课程组提供的输出函数进行了线程安全的封装,保证不会出现时间戳递减的情况出现。

hw5.drawiosequence

第二次作业

第二次作业仍然保持了第一次作业的生产者——消费者模型,对于新增的横向电梯,我仿照Elevator类实现了Ring类,并为每一层设置了一个消息队列,所有同一层的电梯都绑定到这一层的消息队列。

由于本次作业要求支持新增电梯,于是我把Main类中对Elevator和RequestQueue的初始化移动到了Input的构造函数中完成。并且输入的新增电梯请求也由Input类来处理,这样就把电梯线程的调度都交给了Input线程,Main类只负责初始化Input类,并把它启动起来。

除此之外,我还改写了Output类,采用了单例模式,减少了结构耦合,使得代码更加简洁、方便修改。

hw6.drawio

sequence

第三次作业

在第三次作业中,我并没有采用上机提供的流水线架构,而是改写了PersonRequest类,从而保持了生产者——消费者模型,具体实现如下:

我定义了一个新的Person类,Person类的构造函数以PersonRequest类为参数,相当于对官方提供的PersongRequest类进行了二次封装。我添加了两个属性:tarBuilding和tarFloor,表示当前这一次移动的目标楼座和目标楼层。保证(fromBuilding == tarBuilding) ^ (fromFloor == tarFloor) = 1。每次电梯停靠的时候,一个Person出电梯,会立刻调用自身的update方法,找到自己下一次移动的目标并且把自己放到对应的消息队列之中。

之所以这样设计,是因为我觉得这样更符合真实情况。因为调度器这个东西在现实世界中并不存在,真正具有自由意志的是人。一个人想从A座1楼到达C座5楼,是应该让电梯为他安排好道路,还是让人自己来选择道路?我觉得后者更加贴近现实,而我认为面向对象的精髓之一就是对现实的模仿。

hw7.drawio

sequence

四、分析Bug

第一次作业

第一次作业是前两个单元最简单的一次,但是很可惜,我还是在强测中挂了一个点。原因有两点:其一,我控制开关门的代码有问题,如果电梯已满员又无人下电梯,但是这一层有人想上电梯,电梯仍然会“虚空开门”,导致无谓的耗时。其二、我的捎带没有强制要求同向,也导致了电梯性能的下降。两点加在一起造成了RTLE。

第二次作业

第二次作业在课下我遇到了一个会导致轮询的bug。这个在前文其实已经提到了,就是如果过多的使用notifyAll方法,会导致电梯频繁被唤醒,最终导致CTLE,解决方法就是只在有新的请求到来时才notifyAll。

第三次作业

第三次作业我仍然在课下发现了一个会导致轮询的bug。由于横向电梯并不能在所有的楼座停靠,所以在判断调用wait方法的条件时,应该只检查是否还存在该横向电梯能够接到并送到的请求,而不是检查整个消息队列是否为空。否则会导致线程无法进入阻塞队列,导致轮询。

五、Hack策略

Hack策略主要还是根据自己在课下犯得一些共性错误构造几组数据去碰碰运气。三次作业总共刀中4次,效果普普通通。测试线程安全主要是用Linux下的time命令检查CPU时间。这一单元作业的正确性都比较容易保证,真正可能翻车的主要还是线程安全问题,所以测试也应该注重这一方面。

六、心得体会

线程安全:线程安全是多线程程序设计的重中之重,也是多线程编程相较于单线程编程难度陡增的主要原因。通过本单元的学习,我掌握了基本的保护线程安全的方式,也能够更加敏锐地发现可能威胁到线程安全的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