OO第二单元总结

第二单元总结

Tags: OO

线程同步方法

1)为什么需要线程同步?

你是否曾经遇到过这样的情形,尽管自己的房间很乱,但是自己依然能够在乱哄哄的房间里找的自己的某件物品,而当你的母亲帮你收拾了房间之后,许多东西都找不到了。

让我们分析这个过程,对于某件物品来说,它的位置对你是一个信息,如果你每一次移动了它之后都能记住它的位置,那么你永远都能够找到它,但是问题在于,如果它在你不知情的情况下发生了移动,那么你依据上次记住的位置去寻找它变化产生迷惑。

线程同步的来源也类似于此,并发的多个线程可能对同一些数据进行操作,如果不加以协调,那么有些时候,某些线程由于数据变动便会茫然无措,发生运行错误。这时,便需要程序员来对不同线程来进行协调,使得它们相互之间不致影响。

2)如何进行线程同步?

进行线程同步的一个思路是,既然一个数据可能被并发的多个线程以未知的顺序进行读写,那么我们是否可以要求线程以固定的顺序执行,答案是:不行。原因是并发线程之间的执行顺序是未知的,被封装于操作系统的黑盒之中。那么现在不能对线程进行要求,我们便可以尝试对数据的访问和修改进行限制。在Java中提供的方法便是锁机制。其关键想法是线程在访问某些被共享的数据时,对数据上“锁”使之无法被除上锁的线程以外的线程访问。这样的做法保证了在某个时刻只有一个线程对共享数据进行操作。(但是依然无法保证操作的顺序)

3)具体实现

在Java所提供的语法中,synchornize关键字用来实现对指定对象加锁,它既可以直接指定某个具体的锁对象,也可以作为某个方法的关键字来实现线程安全类。

调度器设计

我在三次作业中都实现了调度器,调度器本身作为一个线程存在,它读取总的请求队列中的请求再分发给各个电梯,在最开始的时候,我没有考虑太多,按照ALS算法设计了调度器,导致许多个测试点直接超时,在优化了调度器的算法之后,修复了第一次电梯作业里的bug。这里调度器线程与电梯线程的交互方式是互斥访问requestQueue这个共享变量,也就是说,对于电梯来说,它只能感受到requestQueue的变化,并不能知道调度器的存在,在这之后的两次作业中,这一方式都没有发生改变,这也导致了电梯的性能有限,原因是它并不是实时读取标准输入的,而需要经过调度器的转发并且处理完当前请求才会再次从waitQueue中读取请求,再好的调度算法也只是尝试着预测未来,但是这些都远不如把握当下(指实时响应请求)。

调度器代码如下:

private void arrangeRequest() {
        while (true) {
            if (tryToReturn()) {
                return;
            }
            ArrayList<PersonRequest> thisRequest = getPassenger();
            synchronized (elevators) {
                while (elevators.size() > requestQueues.size()) {
                    generateNewElevator();
                }
                elevators.notifyAll();
            }
            if (thisRequest == null) {
                waitForAllArrive();
                if (waitQueue.getSize() == 0) {
                    waitQueue.setEmpty();
                    notifyAllElevator();
                    return;
                }
                thisRequest = getPassenger();
            }
            dealRequest(thisRequest);
            if (waitQueue.isEnd() && waitQueue.getSize() == 0) {
                waitForAllArrive();
                if (waitQueue.getSize() == 0) {
                    waitQueue.setEmpty();
                }
            }
        }
    }

private ArrayList<PersonRequest> getPassenger() {
        long startTime = System.currentTimeMillis();
        if (arrivePattern.equals("Morning")) {
            waitForPassenger(startTime, 6000);
        } else if (arrivePattern.equals("Random")) {
            waitForPassenger(startTime, 2000);
        } else {
            synchronized (waitQueue) {
                while (!waitQueue.isEnd()) {
                    try {
                        waitQueue.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                waitQueue.notifyAll();
            }
        }
        PersonRequest personRequest = waitQueue.getRequest();
        if (personRequest == null) {
            return null;
        } else {
            waitQueue.addRequest(personRequest);
            return choosePassenger();
        }
    }

private ArrayList<PersonRequest> choosePassenger() {
        ArrayList<PersonRequest> thisRequest = new ArrayList<>();
        ArrayList<PersonRequest> upPassenger = new ArrayList<>();
        ArrayList<PersonRequest> downPassenger = new ArrayList<>();
        synchronized (waitQueue) {
            for (PersonRequest person : waitQueue.getRequestQueue()) {
                if (isUpPassenger(person)) {
                    upPassenger.add(person);
                } else {
                    downPassenger.add(person);
                }
            }
            if (upPassenger.size() < downPassenger.size()) {
                for (PersonRequest person : downPassenger) {
                    thisRequest.add(person);
                    waitQueue.getRequestQueue().remove(person);
                }
            } else {
                for (PersonRequest person : upPassenger) {
                    thisRequest.add(person);
                    waitQueue.getRequestQueue().remove(person);
                }
            }
            waitQueue.notifyAll();
        }
        return thisRequest;
    }

private void dealRequest(ArrayList<PersonRequest> personRequests) {
        pushRequestToC(personRequests);
        if (personRequests.size() == 0) {
            return;
        }
        pushRequestForB(personRequests);
        if (personRequests.size() == 0) {
            return;
        }
        pushRequestForA(personRequests);
        if (personRequests.size() != 0) {
            for (PersonRequest personRequest : personRequests) {
                waitQueue.addRequest(personRequest);
            }
            try {
                sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

架构设计的可扩展性

第三次的架构设计,经过前两次强测和自测之后,在安全性方面已经做了很多了,唯一遗憾的是没有实现实时响应请求的功能,这里介绍以下我的调度策略,也许就能展示我的电梯为什么无法实时处理请求的原因了。我的电梯调度策略是每一次都让电梯处理一个方向的请求,在它处理某个方向的请求时,尽量将另一个方向的请求分发给它,这样就不会让它来回跑,电梯在处理完该方向请求后,才会查看调度器是否分发了新的请求,而不是在行走的过程中实时查看调度器分发来的请求。这样简化了(其实也不一定)电梯程序,但是也很大程度上牺牲了性能。

本次作业的UML类图如下:

UML顺序图(主线程仅仅实例化和启动了各个线程):

由于采用了策略和机制相分离的设计思想,也就是调度策略和电梯运行机制相分离,该架构的可扩展性很强,只需要单独改动Elevator或调度器中的方法即可实现不同的调度策略。

程序中的bug

1)第一次作业

第一次作业中的bug全部是由于电梯运行过慢导致的,电梯过慢的原因是我的调度算法是ALS调度,还写得比data_checker的慢,在稍稍改动了调度算法后便通过了测试点。

2)第二次作业

第二次作业中的bug一个是由于线程安全问题引发的,另外两个是在极限数据中超时,同时还发现了由于线程同步导致了某些电梯线程提前死亡,这样使得性能变得很差。

3)第三次作业

第三次作业中没有产生bug,但是性能由于前面所述的原因很差。

分析他人程序中存在的bug的策略

我主要是构造极限样例的方法来找出bug,比如,在第一次作业的时候,在Random模式下构造让电梯在1-20层来回跑的数据,再构造一组全部由10层出发,前往1或20层的数据,以此类推,看看程序会不会超时或者有线程安全问题。到了第二次和第三次作业,我就根据强测数据的构造方式,产生类似的数据进行测试。

心得体会

经历了这一次作业后,我更加深入的认识了面向对象的威力所在,同时我还发现UNIX设计哲学在程序同样有大用处,这样设计出来的程序可以非常优美,我想最高境界就是所谓的“代码即注释”。

UNIX设计哲学的信条有以下几条:

  • 模块原则,对应面向对象中对象用消息相互通信的思想;
  • 隔离原则,也就是策略和机制相分离,本次作业中电梯的运行机制和调度器的调度策略是想分开的;
  • 表达原则,用数据结构表达逻辑,而不是用过程控制表达逻辑;

本文作者:Max_Season

本文链接:https://www.cnblogs.com/maxorao/p/14710922.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   Max_Season  阅读(70)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起