[BUAA OO Unit 2]单元总结

白新宇-23371216 2025-04-15 22:45:42

目录

一、前言

二、同步块的设置和锁的选择

三、调度器设计与线程交互

1、第五次作业

2、第六次作业

3、第七次作业

四、结合线程协同的架构模式

1、第五次作业

(1)、UML类图

 (2)、UML协作图

2、第六次作业

(1)、UML类图

(2)、UML协作图

3、第七次作业

(1)、UML类图

(2)、UML协作图 

五、双轿厢改造与电梯碰撞

六、bug发现与修复

七、心得体会

1、线程安全

2、层次化设计


一、前言

本文将以笔者在面向对象第二单元的三次作业中的思考感悟为基础,对2025年OO第二单元的代码的架构搭建与程序设计思路做简单的分享,欢迎指出问题与交流思路。

二、同步块的设置和锁的选择

本单元三次作业的的锁均采用由synchronized修饰的的同步方法

对项目中的共享对象的所有方法添加synchronized修饰,保证了共享对象不会产生读写冲突以及保证了双轿厢电梯升级过程中和升级后的输出冲突问题。

锁与同步块中处理语句之间的关系

当一个线程开始进行共享对象的同步方法时,其他线程对于该共享对象的同步方法调用被阻塞,即同一时间共享对象的同步方法只有一个会在运行中,避免共享对象的读写冲突。同时,可以在同步方法中加入wait()-notify()机制以避免线程的轮询。

三、调度器设计与线程交互

调度策略

在三次作业中,电梯的纵向运行策略均为ALS策略,电梯的横向分配策略均为平均分配策略(以适应时间、电量等多个性能指标)。线程交互基本是基于两个生产消费者模型。


1、第五次作业

输入线程

与调度器线程共享一个请求队列,输入线程不断接受输入,如果没有输入就阻塞等待;如果有输入就将输入添加到请求队列中;输入结束就通知设置请求队列输入结束,并自身退出循环结束线程。

调度器线程

与输入线程共享一个请求队列,与电梯线程共享一个候乘表。调度器线程不断从请求队列中获取请求,如果获取到了请求,就根据请求的电梯序号将请求添加到对应的电梯的候乘表。当调度器线程与输入线程共享的请求队列被输入线程标记为输入结束,且请求队列为空时,将六个电梯线程的候乘表设置为工作结束,并退出循环,结束线程。

电梯线程

 

与调度器线程共享一个候乘表,并拥有乘客队列。在电梯的run方法循环中:

1.首先判断候乘表和乘客队列是否为空,若候乘表和乘客队列为空再判断是否候乘表工作结束,若候乘表工作结束则退出循环结束线程,否则电梯wait等待乘客。

2.接着根据策略类的判断设置电梯运行方向

3.再判断是否有乘客可以进入和是否有乘客需要离开,若有则开门并进行乘客离开和进入的操作(应当先离开再进入,否则可能出现拒载的情况)。

4.在电梯开门关门后再进行一次乘客队列为空的判断(可能出现乘客已经全部离开的情况,若不判断,则电梯会以旧的方向到新的一层)

5.根据策略类给出的方向进行电梯移动。


2、第六次作业

输入线程

相比上一次作业新增了临时调度请求,其他不变。

调度器线程

新增了临时调度请求的分配,在线程结束的条件判断以及getRequest()方法的进入wait()的条件判断上加入与临时调度请求的相关内容。

电梯线程

在电梯的run方法循环中:

新增了requestQueue为电梯线程与调度器线程的共享对象以实现

1.首先判断候乘表的乘客请求与临时调度请求和乘客队列是否为空,若候乘表和乘客队列为空再判断是否候乘表工作结束,若候乘表工作结束则退出循环结束线程,否则电梯wait等待乘客。

2.判断候乘表里是否有临时调度请求(若有则进入临时调度的处理方法,若无则向下进行)

2.接着根据策略类的判断设置电梯运行方向

3.再判断是否有乘客可以进入和是否有乘客需要离开,若有则开门并进行乘客离开和进入的操作(应当先离开再进入,否则可能出现拒载的情况)。

4.在电梯开门关门后再进行一次乘客队列为空的判断(可能出现乘客已经全部离开的情况,若不判断,则电梯会以旧的方向到新的一层)

5.根据策略类给出的方向进行电梯移动。


3、第七次作业

输入线程

较上一次作业增加了双轿厢电梯升级请求,其他不变。

调度器线程

新增了双轿厢电梯升级请求的分配,在分配过程中实现满足双轿厢电梯的楼层限制的的分配逻辑,并将不能一次性到达目标楼层的乘客请求记为一个集合,该集合参与调度器线程的结束判断以及getRequest()方法的wait()条件判断。

电梯线程

较上次作业新加入双轿厢电梯升级请求的处理过程,加入Elevatorshaft作为双轿厢两个电梯的共享对象,其升级处理以及处理后的电梯移动均在该类中实现(利用synchronized修饰的同步方法来避免电梯碰撞以及升级过程中的输出与电梯运行逻辑的正确进行)。

四、结合线程协同的架构模式


1、第五次作业

(1)、UML类图

 (2)、UML协作图


 

2、第六次作业

(1)、UML类图

(2)、UML协作图


 

3、第七次作业

(1)、UML类图

(2)、UML协作图 

 稳定与易变

MainClass与InputHandler基本稳定,而Scheduler、Elevator、RequestQueue易变(由于新增请求类型与新增请求处理方式以及不同的调度实现)。

五、双轿厢改造与电梯碰撞

我通过构建ElevatorShaft类,将其作为改造的两个电梯的共享对象,并在其中实现upRequestDoneA()、upRequestDoneB()、moveA()、moveB()同步方法。

在upRequestDoneA()、upRequestDoneB()中通过wait()-notifyAll()的机制实现双轿厢的同步开始改造以及电梯运行逻辑与改造请求输出的相适配。

public synchronized void upRequestDoneA(
            UpdateRequest updateRequest, LinkedList<PeopleRequest> waiting) {
        elevatorA.setDirection(0);
        elevatorA.setSpeed(0.2);
        LinkedList<PeopleRequest> outF = new LinkedList<>();
        if (!elevatorA.getPassengers().isEmpty()) {
            elevatorA.openDoor();
            for (PeopleRequest peopleRequest : elevatorA.getPassengers()) {
                elevatorA.passengerOut(peopleRequest);
                if (!peopleRequest.getToFloor().equals(elevatorA.getCurrentFloor())) {
                    peopleRequest.setFromFloor(elevatorA.getCurrentFloor());
                    outF.add(peopleRequest);
                    //System.out.println(peopleRequest.getPersonId() + "B");
                }
            }
            elevatorA.getPassengers().clear();
            elevatorA.closeDoor();
        }
        opA = 1;
        notifyAll();
        if (opB == 0) {
            try {
                wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        for (PeopleRequest peopleRequest : outF) {
            requestQueue.addRequest(peopleRequest);
        }
        if (!waiting.isEmpty()) {
            for (PeopleRequest peopleRequest : waiting) {
                requestQueue.addFRequest(peopleRequest);
                //System.out.println(peopleRequest.getPersonId() + "B");
            }
        }
        elevatorA.setCurrentFloor(elevatorA.getWaitingTable().changeNum(
                elevatorA.getWaitingTable().changeFu(updateRequest.getTransferFloor()) + 1));
        elevatorA.setLowFloor(updateRequest.getTransferFloor());
        elevatorA.setOp(1);
        elevatorA.setIdu(elevatorB.getId());
        elevatorA.getWaitingTable().removeU();
    }

    public synchronized void upRequestDoneB(
            UpdateRequest updateRequest, LinkedList<PeopleRequest> waiting) {
        elevatorB.setDirection(0);
        elevatorB.setSpeed(0.2);
        LinkedList<PeopleRequest> outF = new LinkedList<>();
        if (!elevatorB.getPassengers().isEmpty()) {
            elevatorB.openDoor();
            for (PeopleRequest peopleRequest : elevatorB.getPassengers()) {
                elevatorB.passengerOut(peopleRequest);
                if (!peopleRequest.getToFloor().equals(elevatorB.getCurrentFloor())) {
                    peopleRequest.setFromFloor(elevatorB.getCurrentFloor());
                    outF.add(peopleRequest);
                    //System.out.println(peopleRequest.getPersonId() + "B");
                }
            }
            elevatorB.getPassengers().clear();
            elevatorB.closeDoor();
        }
        if (opA == 0) {
            try {
                wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        TimableOutput.println("UPDATE-BEGIN-" +
                updateRequest.getElevatorAId() + "-" + updateRequest.getElevatorBId());
        for (PeopleRequest peopleRequest : outF) {
            requestQueue.addRequest(peopleRequest);
        }
        if (!waiting.isEmpty()) {
            for (PeopleRequest peopleRequest : waiting) {
                requestQueue.addFRequest(peopleRequest);
                //System.out.println(peopleRequest.getPersonId() + "B");
            }
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        elevatorB.setCurrentFloor(elevatorB.getWaitingTable().changeNum(
                elevatorB.getWaitingTable().changeFu(updateRequest.getTransferFloor()) - 1));
        TimableOutput.println("UPDATE-END-" +
                updateRequest.getElevatorAId() + "-" + updateRequest.getElevatorBId());
        opB = 1;
        notifyAll();
        elevatorB.setHighFloor(updateRequest.getTransferFloor());
        elevatorB.setOp(-1);
        elevatorB.setIdu(elevatorA.getId());
        elevatorB.getWaitingTable().removeU();
    }

用moveA()、moveB()代替之前的电梯移动函数已实现同一时间只有一个move()方法在运行,来避免电梯碰撞问题。

public synchronized void moveA() {
        int time = (int) (elevatorA.getSpeed() * 1000);
        if (elevatorA.getDirection() == 1 &&
                !elevatorA.getCurrentFloor().equals(elevatorA.getHighFloor())) {
            try {
                Thread.sleep(time);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            elevatorA.setCurrentFloor(elevatorA.getWaitingTable().changeNum(
                    elevatorA.getWaitingTable().changeFu(elevatorA.getCurrentFloor()) + 1));
            elevatorA.arrive();
        } else if (elevatorA.getDirection() == -1 &&
                !elevatorA.getCurrentFloor().equals(elevatorA.getLowFloor())) {
            try {
                Thread.sleep(time);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            elevatorA.setCurrentFloor(elevatorA.getWaitingTable().changeNum(
                    elevatorA.getWaitingTable().changeFu(elevatorA.getCurrentFloor()) - 1));
            elevatorA.arrive();
        }
        if (elevatorA.getCurrentFloor().equals(elevatorA.getLowFloor())) {
            if (!elevatorA.getPassengers().isEmpty() || elevatorA.getWaitingTable().hasFloorRequest(
                    elevatorA.getOp(),elevatorA.getPassengers().size(),
                    elevatorA.getCurrentFloor())) {
                elevatorA.openDoor();
                for (PeopleRequest peopleRequest : elevatorA.getPassengers()) {
                    elevatorA.passengerOut(peopleRequest);
                    if (!peopleRequest.getToFloor().equals(elevatorA.getCurrentFloor())) {
                        peopleRequest.setFromFloor(elevatorA.getCurrentFloor());
                        requestQueue.addFRequest(peopleRequest);
                        //System.out.println(peopleRequest.getPersonId() + "B");
                    }
                }
                elevatorA.getPassengers().clear();
                elevatorA.getWaitingTable().pickUp(elevatorA.getOp(),
                        elevatorA.getCurrentFloor(), elevatorA.getPassengers(),elevatorA.getId());
                elevatorA.closeDoor();
            }
            try {
                Thread.sleep(time);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            elevatorA.setCurrentFloor(elevatorA.getWaitingTable().changeNum(
                    elevatorA.getWaitingTable().changeFu(elevatorA.getCurrentFloor()) + 1));
            elevatorA.arrive();
        }
    }

    public synchronized void moveB() {
        int time = (int) (elevatorB.getSpeed() * 1000);
        if (elevatorB.getDirection() == 1 &&
                !elevatorB.getCurrentFloor().equals(elevatorB.getHighFloor())) {
            try {
                Thread.sleep(time);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            elevatorB.setCurrentFloor(elevatorB.getWaitingTable().changeNum(
                    elevatorB.getWaitingTable().changeFu(elevatorB.getCurrentFloor()) + 1));
            elevatorB.arrive();
        } else if (elevatorB.getDirection() == -1 &&
                !elevatorB.getCurrentFloor().equals(elevatorB.getLowFloor())) {
            try {
                Thread.sleep(time);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            elevatorB.setCurrentFloor(elevatorB.getWaitingTable().changeNum(
                    elevatorB.getWaitingTable().changeFu(elevatorB.getCurrentFloor()) - 1));
            elevatorB.arrive();
        }
        if (elevatorB.getCurrentFloor().equals(elevatorB.getHighFloor())) {
            if (!elevatorB.getPassengers().isEmpty() || elevatorB.getWaitingTable().hasFloorRequest(
                    elevatorB.getOp(),elevatorB.getPassengers().size(),
                    elevatorB.getCurrentFloor())) {
                elevatorB.openDoor();
                for (PeopleRequest peopleRequest : elevatorB.getPassengers()) {
                    elevatorB.passengerOut(peopleRequest);
                    if (!peopleRequest.getToFloor().equals(elevatorB.getCurrentFloor())) {
                        peopleRequest.setFromFloor(elevatorB.getCurrentFloor());
                        requestQueue.addFRequest(peopleRequest);
                        //System.out.println(peopleRequest.getPersonId() + "B");
                    }
                }
                elevatorB.getPassengers().clear();
                elevatorB.getWaitingTable().pickUp(elevatorB.getOp(),
                        elevatorB.getCurrentFloor(), elevatorB.getPassengers(),elevatorB.getId());
                elevatorB.closeDoor();
            }
            try {
                Thread.sleep(time);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            elevatorB.setCurrentFloor(elevatorB.getWaitingTable().changeNum(
                    elevatorB.getWaitingTable().changeFu(elevatorB.getCurrentFloor()) - 1));
            elevatorB.arrive();
        }
    }

六、bug发现与修复

第五次作业

Bug为由于电梯上下完乘客后,电梯中没有乘客时,由于缺少该判断而产生的电梯在旧方向上多走一层的问题,加上相关判断后continue即可。

第六次作业

Bug为由于receive输出位置有误而导致的Wrong_State问题以及由于getRequest()中wait()的条件判断中缺少临时调度请求为空的情况而产生的Time error。

将receive的输出位置添加至pickup方法中以解决问题一,将wait()的条件判断加上临时调度请求为空的情况以解决问题二。

第七次作业

Bug为由于getRequest()中wait()条件判断的不完整而导致的wait()提前失效而产生的Scheduler轮询问题。

修改条件从isEmpty() && !isFinished && SisEmpty() && UisEmpty()为isEmpty() && SisEmpty() && UisEmpty() && (!isFinished || !FisEmpty())以解决该问题。

七、心得体会

1、线程安全

要在合适的地方加锁,不要滥用加锁,确定好哪段代码是临界区(尽可能小,提高并发效率),将线程安全类与普通类分离开来。也不要滥用notifyall(可能导致轮询)。缕清各线程之间的时序问题并利用同步方法保证线程安全。

2、层次化设计

采用了简单了生产者消费者模型和单例模式。

整体的框架在第一次作业时已经建立,后续的作业只是在第一次作业的基础上迭代开发,一路沿用上次作业的架构,导致类的耦合度有点高,并且是面向具体编程,也没有面向接口编程,希望下个单元能够改进。

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

269

社区成员

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

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