OO第二单元课后总结

黄拓远-22371180 学生 2024-04-19 21:15:50

目录

 

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

1.1 同步方法

1.2 同步对象

2. 相关设计与类图分享

2.1 架构设计

2.2 UML类图分享

2.2 UML时序图分享

3. 电梯的运行策略

4. 三次作业中调度器的设计

4.1 第一次作业

4.2 第二次作业

4.3 第三次作业

5 三次作业中稳定的内容和易变的内容

5.1 稳定的内容

5.2 易变的内容

6 第三次作业中如何实现双轿厢不相撞

7 三次作业中遇到的bug以及debug方法

7.1 遇到的bug

7.2 解决方法

8 心得体会


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

1.1 同步方法

在这个单元的作业中,进程与进程之间有许多共享对象,如何处理好这些共享对象是本单元作业的一大难点,同时也是本单元需要学习的重点。大部分的共享对象,我都新建了一个类用于存放这些共享对象,并且写了方法来操作这些对象。对于读写方法,我使用了synchronized语句用于将同步代码块保护起来。如:

public synchronized void Set() {
    // 同步代码块
}

这样做的好处就是可以做到数据安全,保证在读写的时候只有一个进程在操作。而且,这种方法比同步对象的方法要更加安全,因为同步对象要将代码写在进程类中,在很多个进程之间有数据交互的时候,同步对象的方法往往会造成进程死锁但是又难以复现的问题,逻辑上很难不出问题,而同步方法只是对共享对象的类进行操作,逻辑上更加清晰。

1.2 同步对象

就向上面所说的那样,同步对象就是使用synchronized将一个对象给锁起来,这样的做法一般放在进程当中,如:

synchronized(this) {
        // 同步代码块
    }

这么一般是为了保证线程中同步代码块的原子性,如上在对this操作的时候,同步代码块中this的状态是不会被其他线程改变的。而我对于同步对象的运用一般是进行线程的等待,如:

//需要等待的线程
synchronized(lock) {
     try {
                        lock.wait(50000);
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }   
    }
//其他线程
synchronized (lock) {
            lock.notifyAll();
        }

我在这个处理上面有点考虑不周,如果把同步对象换成同步方法或许实现逻辑上会更加清晰。

2. 相关设计与类图分享

2.1 架构设计

本次作业我的架构是采用了消费者-生产者模式,这是一种常见的并发设计模式,用于解决多线程环境下生产者和消费者之间的协作问题,其中生产者负责生成数据并放入共享队列中,而消费者则负责从队列中取出数据进行处理。

 

2.2 UML类图分享

2.2 UML时序图分享

3. 电梯的运行策略

在第一次作业中,我的电梯有五个状态,分别是open_still_move_up(表示这个时候电梯开门,但是内部或者外部依然有向上的请求),move_up(表示电梯这个时候正在向上行驶),open_still_move_down(表示这个时候电梯开门,但是内部或者外部依然有向下的请求),move_down(表示电梯这个时候正在向下行驶),close_and_wait(表示这个时候电梯内外部没有请求)。

在open_still_move_up状态下,电梯会开门并且先让在这一层出电梯的人先出,然后让在这一层进入电梯的人进入。然后根据此时内外部的请求判断接下来应该转换为什么状态。

在move_up状态下,电梯会上行一层,然后根据当前楼层是否有同向请求或者是否有乘客在该楼层出电梯判断接下来要不要开门。

在open_still_move_down状态下,与open_still_move_up同理。

在move_down状态下,与move_up状态同理。

在close_and_wait状态下,电梯会先判断是否要结束线程,若不结束,接着会判断是否有请求,如果没有,就会wait等待notify。

在第二次作业中,加入了reset操作,因此我也给电梯加了一个reset状态,在这个状态下,电梯会先开门,然后将自己内部所有的人员清空,并且还清空外部请求使其重新被分配。接着开始reset,结束之后进入close_and_wait状态。

第三次作业时电梯运行策略没有过多改变,主要是reset加入了doublereset操作,并且一个乘客进入电梯后,如果电梯没办法将其送至目标楼层,则将其送至电梯能到达的最近一层。我的电梯状态转换图如下:

 

4. 三次作业中调度器的设计

4.1 第一次作业

在第一次作业中,因为乘客的请求是包括的电梯id的,所以我的调度器便是按乘客要求分配,此时进程间的数据还是单向的,因为调度器并不需要从电梯的状态来判断分给哪一部电梯,直接根据请求分配即可。

4.2 第二次作业

在第二次作业中,由于这个时候电梯可以任意选择了,所以分配策略有了很大改变。我的大致方法是这样的,首先遍历所有的电梯,找到可以捎带并且距离最近乘客数量少于最大数量的电梯,如若这样的电梯不存在,我便会再遍历所有电梯,找到此时处于close_and_wait状态并且距离最近的电梯分配给他,如果这样的还不存在,那么便将其分配给不是reset状态并且内外请求最少的电梯,如果还是不存在的话,说明6部电梯都处于reset状态,那么此时不可以对任何一个电梯分配乘客,因此调度器应该wait,等待电梯状态改变从而notify。

4.3 第三次作业

在第三次作业中,我的调度策略没有任何改变,主要是遍历电梯从原来的6部变成了现在的大于等于6部。逻辑和第二次作业一样。

5 三次作业中稳定的内容和易变的内容

5.1 稳定的内容

三次作业中,电梯的运行策略较为稳定,在第一次写好了之后基本上不会改变,主要是第二次加个reset状态,第三次稍微改变一下,因为电梯和调度无关,不管是单部电梯还是多部电梯,运行策略都是一样的,因此在第一次就会构建的比较成熟。

5.2 易变的内容

在三次作业中,电梯的调度策略容易改变,因为在第一次作业中,几乎就没有调度策略,而且进程之间数据的交换为单向。第二次作业时调度几乎是从零开始,线程之间要有数据的双向交换,需要加入很多数据安全的考虑。第三次作业加入了双轿厢电梯,但是调度的本质还是一样的,所以第三次和第二次区别也不大,主要还是第一次和第二次差别较大。

6 第三次作业中如何实现双轿厢不相撞

在第三次作业中,双轿厢电梯只能有一个电梯处于换乘层,而在我的设计之中,线程是可以访问所有电梯的数据的,我将电梯的数据放在一个新开的ElavatorData类中,这个类存储了所有电梯的数据,可以供所有线程访问,因此,当我的其中一个双轿厢电梯到了换乘层的时候,它会访问它的兄弟电梯的数据,看看其是否在换乘层,如果在的话,则会等待。同样的,当其离开换乘层的时候,它会notify它的兄弟电梯。

7 三次作业中遇到的bug以及debug方法

7.1 遇到的bug

1、电梯reset_begin之后依然receive。

2、死锁。

7.2 解决方法

1、出现第一种问题主要是因为分配的原子性没有做好,因为在取电梯状态时,如果这个时候就把锁给出去了的话,电梯状态改变(假设改变为了reset),但是调度器却依然当其不是reset而考虑给其分配电梯。解决方法便是在电梯完完全全将人分出去了之后再将锁给出去,也就是保证调度器分配的原子性。

2、死锁,而且难以复现的那种情况,这种情况下好的解决策略便是重新检查逻辑,或者将代码进行重构,尽量使用同步方法而不是同步对象。这样逻辑能够更为清晰,除此之外我也不知道有什么好的方法可以解决难以复现的问题。

8 心得体会

本单元作业感觉还是比较难的,因为如果一开始设计的不好的话,后面的迭代就会出现很多问题。如果对于锁的理解不够深刻的话,很容易出现死锁。但是本单元的收获还是很大的,能够学习并且掌握与线程相关的知识点,总的来说还是痛并快乐着的。

...全文
47 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

301

社区成员

发帖
与我相关
我的任务
社区描述
2023年北航面向对象设计与构造
学习 高校
社区管理员
  • YannaZhang
  • CajZella
  • C_ecelia
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧