oo第二单元总结

对于锁的理解

对于这一单元来说,多线程可能会导致的线程不安全的问题,我认为有很大一部分原因是对锁的不够充分理解造成的,下面是我对锁的一些理解。

  • 无论是在os还是在oo的学习过程中,我们知道,任何一个对象(lock)都可以作为一个锁,通过这个锁来实现临界资源的互斥,所以理所当然,每一个对象也就实现了lock.wait()lock.notifyAll()两个方法。

    需要注意的两点:

    1.  lock.lock()
       try {
       	//your code
       } finally {
       	lock.unlock()
       }
      

      这样无论在try块中的代码return或者产生了异常,都能够保证释放锁。

    2. wait的方法是在当前的pc阻塞,所以被唤醒后,会从wait后的语句开始执行。

  • 对于我们熟悉的synchronized 方法块,实际上等同与获得这个方法所在对象的锁,所以我们在这个方法块中可以直接使用的wait()notifyAll()方法也就是调用当前对象的这两个方法。

对于锁有了一定的充分的理解之后,如果这一单元的作用架构不是很复杂的话,就不容易出现线程的安全问题。

三次作业的设计结构

三次作业大致采用了相同的结构,即一个线程用来处理输入,一个线程作为电梯,一个调度器,电梯采用了look算法,调度器中包含请求队列,向队列中增加请求,从队列中获得请求的方法。

下面是三次作业分别的一些具体处理细节:

  1. 第一次作业中,请求队列采用了阻塞队列。
  2. 第二次作业,改为了waitnotifyAll的方法。
  3. 第三次作业,改动较大,在前两次作业中是把调度器作为了输入和电梯的属性,而在第三次作业,因为是由多个电梯,为了实现把电梯注册到调度器中的思想,所以采用了单例模式,把调度器作为一个单例,把电梯个输入作为调度器的属性,对于线程的处理,依然是三个电梯和输入分别对请求队列进行互斥访问。同时为了解决拆分的问题,把request的请求又封装了一个类,在这个类中实现拆分,同时记录需要换成的路线。

三次作业的线程结束策略

对于这一单元的多线程作业来说,多个线程的合理结束往往是引起线程安全问题的一个重要方面。

  1. 第一次作业中,对于输入线程的结束来说,都是读入null后结束,为了能够使电梯获得这一信息,在调度器中包含了一个属性,当输入结束后将这个属性置为true,当电梯获取请求时,如果得到的请求为null同时这个属性为true,说明此时输入结束,同时请求队列中已经没有请求,那么就可以让电梯线程结束。

  2. 第二次作业与第一次作业的处理方式相同,不再赘述。

  3. 第三次作业,同样因为多个电梯可以到达的楼层不同,所以即便是请求队列中仍然含有这个电梯不能到达的请求,这个电梯也应该wait,所以采用前两次的方法就会导致,无法区分当前电梯获得了一个null是应该停止,还是应该wait。所以改变了策略。在电梯中包含一个属性,来记录该电梯是否处于wait状态,当电梯获得了null时,先判断,如果此时输入已经结束,并且另外两个电梯也处于wait状态,那么把所有wait的电梯唤醒,返回hull,使三个电梯征程结束,否则该电梯应该wait。

    while (getRequest == null) {
    	thisElevator.wait = true;
        if (shouleStop) {
            notifyAll();
            return null;
        }
        wait();
    }
    
    boollean shouldStop() {
    	if (three elevators.wait is true and noInput)
    		return true;
    }
    

第一次作业

  1. 首先在设计模式上,对于输入线程,三次作业都是一样的,可以做到复用,但是对于电梯的线程来说,如果要更改算法,就需要对整个代码进行修改,其实可以把电梯基础的行为封装为一个抽象类,对于不同算法的电梯可以从这个类集成,避免了对已有代码的修改。

  2. 对于这次作业产生的bug,由于这次是单电梯,向上面说的,对锁有一定的理解,这次作业基本不会产生错误。

第二次作业

  1. 在设计结构上,和上一次作业基本相同,还是输入线程,调度器,电梯。不同点在于电梯采用了不同的算法。基于上次的经验,为了方便的扩展电梯的不同算法,把电梯的基本功能,如上下楼,开关门等封装为一个baseElevator,从这个类继承,可以实现不同算法的电梯。
  2. 这次产生的bug,由于电梯,调度器,输入。这三者之间的交互依然和上次没有区别,所以没有产生线程安全的问题。只是电梯的逻辑发生了一些变化,所以在自己的测试中也比较容易发现自己的错误。

第三次作业

  1. 在设计结构上,整体思路和前两次相同。依然是输入线程负责向请求队列中添加请求,电梯线程取出请求。但是为了实现多个电梯线程,所以在结构上做了如下一些改变。
    • 调度器:前两次是把调度器作为输入,和电梯的属性。而这次中为了实现把电梯注册进调度器的思想,所以用了单例模式,把输入和电梯作为调度器的属性。在调度器内部开始电梯和输入线程的运行。同时由于单例模式,电梯和输入也可以访问调度器的方法。
    • 请求类:由于这次作业需要换乘,而复杂动态的换乘策略没有想到,所以采用了暴力拆分,在请求类的内部记录换成的路线。
    • 电梯类:电梯的算法依旧是采用上一次的look算法,但是由于需要支持换乘,所以电梯增加了一个方法。在人下电梯时判断需不需要把这个请求重新加入到请求队列。
    • 在判断结束线程方面也做了一些变化,如前面所述。
  2. 对于这次遇到的bug,由于这次的改变相较于上次没有太大的改变,依然是相同的算法,三个电梯竞争请求。但是在线程结束方面也遇到了线程安全的bug,在没有改变结束策略前,因为三个电梯能够到达的楼层不同,所以当当前电梯获得的请求为null时不能判断是应该结束,还是当前没有适合该电梯的请求,所以会造成线程安全问题。所以改变了策略,把结束的条件更改为三个电梯都在wait且输入已经结束,解决了这个bug之后就没有遇到大的bug。

新的体会

  1. 对于设计的结构来说,依然有很大很大很大的问题,虽然由于本单元作业的特性,没有对每次作业都重构,但是也没有做到在继承等方法的基础上实现对功能的扩展,每次对于关键逻辑实则还是相当于完全的重写。对于代码功能来说,没有明确的进行功能的合理划分,实际上是一种打补丁式的方法,即需要那个方法就填上。
  2. 对于多线程的程序来说,大部分的困难都来自对锁的不熟悉,在具体学习了这部分的内容后,难度就减少了很多。对于这一单元的作业来说,如果采用的是朴素的方法,即输入-请求队列-电梯,这种放弃优化的方法,只要注意了线程结束的条件,也不太容易发生线程安全的错误。

所以,对于oo来说,实现作业的正确性不是最主要的,能够提高自己对整体结构的设计,有一个良好的框架才是在之后的作业中应该非常重视的(每次因为时间所迫还是选择先完成了正确性)。

posted @ 2019-04-23 19:53  qsblublu  阅读(138)  评论(0编辑  收藏  举报