yyw-----BUAA-OO-UNIT 2

本文详细讨论了在多线程环境下,如何使用内置锁和显示锁(如ReentrantLock、ReadWriteLock)来确保waitlist和电梯线程之间的线程安全。作者分享了生产者消费者模型的应用,强调了锁的合理使用和层次化设计的重要性,以及在实际编程中遇到的bug和解决方案。
摘要由CSDN通过智能技术生成

同步块的设置和锁的选择

首先总结下锁的知识:

  1. 内置锁 (Synchronized)

  • Synchronized 方法:通过在方法声明上加 synchronized 关键字,可以确保在同一时刻最多只有一个线程能够执行该方法。

  • Synchronized 块:通过 synchronized 关键字和一个锁对象来创建一个同步块,只有获得了锁对象的锁定,线程才能执行这个块内的代码。

  1. 显示锁 (java.util.concurrent.locks.Lock)

  • ReentrantLock:是一个可重入的互斥锁,提供了广泛的锁操作,比内置锁更灵活。

  • ReadWriteLock:读写锁允许多个线程同时读取共享资源,但只有一个线程可以写入。

  • Condition:与 Lock 配合使用,可以分离出不同的 Object.wait() / Object.notify() 队列。

第一次作业中,我们只需要考虑inputThread写入waitmap与DispatcherThread读取waitmap的冲突以及DispatcherThread写入电梯的outlist和电梯的读取自身的outlist的冲突即可,所以我直接给WaitList类的读写方法加了synchronized控制块,同时,在某些需要多次访问WaitList类的地方加上了synchronized控制块。具体可见下图:

如上所示,生产者InputThread从输入中将乘客请求加入大盘子waitmap中,然后消费者DispatcherThread从大盘子中取出乘客请求;之后DispatcherThread转变角色成为生产者并将乘客请求调度给某部电梯线程消费

那么为什么给waitlist上锁呢?(这里的waitlist包括了上图的waitmap和outlist)

  • waitmap作为输入线程和调度器线程的托盘资源,可能涉及这两个线程的同时读写,因此需加上同步锁来保证线程安全

  • 同时waitlist作为调度器线程和电梯线程的托盘资源,必然涉及调度器线程和电梯线程的同时读写,因此需加上同步锁来保证线程安全

第2,3次作业中,我只新增了对elevator对象或elevators容器(包括doubleEleThreads)上锁。具体代码如下:

 synchronized (elevators) {
                         for (Map.Entry<Integer, ElevatorThread> entry : elevators.entrySet()) {
                             ElevatorThread elevatorThread = entry.getValue();
                             elevatorThread.getOutlist().setIsEnd(1);
                         }
                     }
 synchronized (doubleEleThreads) {
                         for (Map.Entry<String, DoubleEleThread> entry : doubleEleThreads.entrySet()) {
                             DoubleEleThread doubleEleThread = entry.getValue();
                             doubleEleThread.getOutlist().setIsEnd(1);
                         }
                     }

代码架构

生产者InputThread从输入中将乘客请求加入大盘子waitmap中,然后消费者DispatcherThread从大盘子中取出乘客请求;之后DispatcherThread转变角色成为生产者并将乘客请求调度给某部电梯线程消费。对于reset请求和resetDc请求,我在ElevatorThread线程中设置了属性reset,doubleEle,当两种重置请求被输入线程解析后,会将对应电梯的两种属性设1,并在电梯中新增属性:newSpeed,newCapacity;然后在电梯线程处理reset请求

那么,在什么时候设置线程的等待状态wait以及什么时候唤醒?当输入未结束但调度器线程无法从大盘子waitmap获得一个乘客请求时,显然此时调度器线程必须等待,直到输入线程将新的乘客请求加入到大盘子waitmap中并将其唤醒;当电梯停在某一层,电梯内外队列都无乘客且外部队列outlist还有可能接收到来自调度器调度的乘客请求时,此时电梯线程必须等待,直到调度器线程将新的乘客请求加入到对应电梯的小盘子outlist中并将其唤醒;

此外,程序何时结束呢?考虑到程序输入结束时,waitmap已经无任何新增的乘客请求,此时可以结束输入线程,同时设置waitmap的属性isEnd为1,代表waitmap已经无任何新增的乘客请求;当waitmap为空且waitmap已经无任何新增的乘客请求时,此时可以结束调度器线程并设置每个电梯线程对应的outlist的属性isEnd为1,代表各个outlist已经无任何新增的乘客请求;当电梯内外都无人时且outlist已经无任何新增的乘客请求时,我们可以结束电梯线程;至此,所有程序结束

作业变化

第一次作业

第一次作业在调度策略上不难,因为由输入指定了对应电梯,重点是在电梯运行策略上,电梯运行采用Look策略,采用走一步看一步的方法,类似于获得建议然后根据建议执行行动。

本单元作业我采用的是生产者--消费者模型,生产者为输入线程,消费者为调度器线程,之后调度器线程又充当生产者,电梯线程充当消费者。在这次作业中,我只对waitlist类加锁;对于锁的大小要适当,如果锁太大,会降低并发性和效率,如果锁太小,可能引发线程安全问题。因此我们要使得锁与同步块中的语句密切相关,比如,waitmap锁只锁住与waitmap密切相关的语句,具体代码如下:

 public int getWaitListSize() {
     lock.readLock().lock();
     try {
         return waitList.size();
     } finally {
         lock.readLock().unlock();
     }
 }
 ​
 public ArrayList<Passenger> getWaitList() {
     lock.readLock().lock();
     try {
         return waitList;
     } finally {
         lock.readLock().unlock();
     }
 }

第二次作业

第二作业由于在调度策略方面不再指定电梯,因此我们需要涉及自己的调度策略,我采用的是按优先级分配策略,为了尽量满足同向捎带要求,这样可以极大的提高性能,具体来说:如果请求乘客的方向与某一电梯方向相同,并且该电梯还未到达请求可的楼层,且该电梯内部乘客和外部乘客之和不少于最大容量,则将该电梯作为优先电梯,如果没有这样的电梯,则均匀分配一个电梯;

在电梯运行策略上与第一次变化不大,只是新增了对reset请求的处理流程;

第三次作业

第三次作业新增了双轿厢电梯,我的处理方法是resetDc请求传递到电梯线程后,先踢出电梯里外的人并将其返回调度器,然后将普通电梯线程删除并新增双轿厢电梯线程,同时为了避免双轿厢电梯相撞,我们设置一个Flag类并添加属性occupied,如果此时双轿厢电梯有一个占用了换乘层,则设置occupied为1,同时若另一电梯想要进入换乘层,则让其wait直到另一电梯离开换乘层。在本次作业中,电梯结束的条件也是最难判断的一点,但好在经过不断地尝试,还是成功了。

bug以及debug方法

  • 电梯超载问题:在判断乘客可以进电梯时忘记考虑容量限制

 [1.6]RESET-DCElevator-1-3-3-0.6
 [1.6]RESET-DCElevator-2-3-3-0.6
 [1.6]RESET-DCElevator-3-3-3-0.6
 [1.6]RESET-DCElevator-4-3-3-0.6
 [1.6]RESET-DCElevator-5-3-3-0.6
 [1.6]RESET-DCElevator-6-3-3-0.6
 [49.9]1-FROM-3-TO-11
 [49.9]2-FROM-3-TO-11
 [49.9]3-FROM-3-TO-11
 [49.9]4-FROM-3-TO-11
 [49.9]5-FROM-3-TO-11
 [49.9]6-FROM-3-TO-11
 [49.9]7-FROM-3-TO-11
 [49.9]8-FROM-3-TO-11
 [49.9]9-FROM-3-TO-11
 [49.9]10-FROM-3-TO-11
 [49.9]11-FROM-3-TO-11
 [49.9]12-FROM-3-TO-11
 [49.9]13-FROM-3-TO-11
 [49.9]14-FROM-3-TO-11
 [49.9]15-FROM-3-TO-11
 [49.9]16-FROM-3-TO-11
 [49.9]17-FROM-3-TO-11
 [49.9]18-FROM-3-TO-11
 [49.9]21-FROM-2-TO-11
 [49.9]22-FROM-2-TO-11
 [49.9]23-FROM-2-TO-11
 [49.9]24-FROM-2-TO-11
 [49.9]25-FROM-2-TO-11
 [49.9]26-FROM-2-TO-11
 [49.9]27-FROM-2-TO-11
 [49.9]28-FROM-2-TO-11
 [49.9]29-FROM-2-TO-11
 [49.9]30-FROM-2-TO-11
 [49.9]31-FROM-2-TO-11
 [49.9]32-FROM-2-TO-11
 [49.9]33-FROM-2-TO-11
 [49.9]34-FROM-2-TO-11
 [49.9]35-FROM-2-TO-11
 [49.9]36-FROM-2-TO-11
 [49.9]37-FROM-2-TO-11
 [49.9]38-FROM-2-TO-11
 [49.9]71-FROM-1-TO-11
 [49.9]72-FROM-1-TO-11
 [49.9]73-FROM-1-TO-11
 [49.9]74-FROM-1-TO-11
 [49.9]75-FROM-1-TO-11
 [49.9]76-FROM-1-TO-11
 [49.9]77-FROM-1-TO-11
 [49.9]78-FROM-1-TO-11
 [49.9]79-FROM-1-TO-11
 [49.9]80-FROM-1-TO-11
 [49.9]81-FROM-1-TO-11
 [49.9]82-FROM-1-TO-11
 [49.9]83-FROM-1-TO-11
 [49.9]84-FROM-1-TO-11
 [49.9]85-FROM-1-TO-11
 [49.9]86-FROM-1-TO-11
 [49.9]87-FROM-1-TO-11
 [49.9]88-FROM-1-TO-11
 [49.9]89-FROM-1-TO-11
 [49.9]90-FROM-1-TO-11
 [49.9]91-FROM-1-TO-11
 [49.9]92-FROM-1-TO-11
  • 程序无法结束的问题:程序结束条件没有正确设置以及电梯一直处于wait状态没有正确唤醒

 //样例一
 [0.6]RESET-Elevator-3-6-0.4
 ​
 //样例二
 [0.2]RESET-DCElevator-2-7-5-0.5
 [0.2]RESET-DCElevator-3-7-5-0.5
  • 双轿厢电梯相撞问题:在结合样例debug后发现双轿厢电梯线程语句执行顺序出现问题,如下图,正确的顺序时先睡,再改变pos并设置occupied,如果反过来,则容易在sleep期间相撞

 if(pos==transferFloor){
                         if(eid.charAt(eid.length()-1)=='A'){
                             sleep(Math.round(speed * 1000));
                             pos--;
                             TimableOutput.println("ARRIVE-"+pos+"-"+eid);
                             isOccupied.setRelease();
                         }else{
                             sleep(Math.round(speed * 1000));
                             pos++;
                             TimableOutput.println("ARRIVE-"+pos+"-"+eid);
                             isOccupied.setRelease();
                         }
                     }

debug方法:

  • 结合打的print输出判断在哪个子模块出问题

  • 目瞪法,再梳理一遍程序的逻辑

  • 结合评测机

心得体会

线程安全:

线程间的共享对象锁的范围要适当,否则要么出现死锁,要么线程不安全;

层次化设计:

类与线程的分离:主要分为输入线程,调度器线程,电梯线程,然后Main负责启动这些线程;

电梯线程的策略类与电梯类分离;

心声:

这次作业主要是hw7完成的不是很理想,反思一下原因,主要是对相应知识的理解不到位,过于依赖评测机(实际上评测机也无法保证自己完全对),以上两点都说明本人写代码之前思考不是那么充分,容易导致一些细节问题的过失。总而言之,这还是警示自己在借鉴的同时不要忘记自己的思考,这样才能成为并超越吧!

  • 25
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值