442
社区成员
发帖
与我相关
我的任务
分享本单元作业主要完成的是一个多线程实时电梯调度模拟系统,熟悉线程的创建、运行等基本操作,熟悉多线程程序的设计方法,掌握线程安全知识并解决线程安全问题,掌握线程之间的交互,强化线程之间的协同设计层次架构。
同步块和锁主要解决的是多线程的数据安全问题,具体表现为,如果对于同一个数据在不同的线程同时需要执行读取或者写入操作,将会造成数据不统一的安全问题。所以为了让程序执行的时候数据统一,我们将每一个线程对数据的访问进行一个上锁的操作,其表现为将这个数据变为仅仅当下上锁的线程可以进行读写,这样可以确定程序执行前后数据的统一性。
读写锁的特点是:读读不互斥、读写互斥、写写互斥。优势在于:(1)多个读锁可以同时执行,相比于普通锁在任何情况下都要排队执行来说,读写锁提高了程序的执行性能;(2)读锁和写锁是互斥排队执行的,这样可以保证了读取操作不会读到写了一半的临时数据。在读多写少的使用场景下,读写锁的优势最大。
在三次作业中:
第五次作业:仅使用了synchronized关键字,执行对共享资源上锁的功能。具体来说有两种使用。
{ }括起来的代码,作用的对象是调用这个代码块的对象。最典型的是电梯进人方法personIn对候乘表的修饰:public void personIn() {
synchronized (waitQueue) {
ArrayList<Person> waitPersons = waitQueue.getReqQueue();
Iterator<Person> iterator = waitPersons.iterator();
while (iterator.hasNext() && curNum < maxCap) {
Person person = iterator.next();
if (...) {
...
iterator.remove();
}
}
}
}
public synchronized void waitForAdd() {
try {
wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
第六次作业:仅对必须要求获取锁的 wait / notify / notifyall 方法保留了 synchronized 修饰,其余均采用读写锁替代 synchronized 关键字,以进行更精细的控制。
public void personIn() {
ArrayList<Person> waitPersons = waitQueue.getReqQueue();
queueLock.writeLock().lock();
try {
ArrayList<Person> waitPersons = waitQueue.getReqQueue();
Iterator<Person> iterator = waitPersons.iterator();
while (iterator.hasNext() && curNum < maxCap) {
Person person = iterator.next();
if (...) {
...
iterator.remove();
}
}
} finally {
queueLock.writeLock().unlock();
}
}
public boolean isEmpty() {
queueLock.readLock().lock();
try {
return reqQueue.isEmpty();
} finally {
queueLock.readLock().unlock();
}
}
第七次作业:大体同第六次作业,而对新增的迭代要求——在任意时刻,任意楼层处于服务中的电梯数量小于等于4部,处于服务中的只接人电梯数量小于等于2部,则使用了信号量 semaphore 机制,只需要使用其 acquire 方法与 release 方法便可以申请“开门资源”与释放“开门资源”,并且在资源不足时自行等待。主要使用在了电梯类的开门方法中:
public void openDoor(boolean type) throws InterruptedException {
if (type) { acOnly.get(curFloor).acquire(); }
serveNum.get(curFloor).acquire();
...
if (!type) { personOut(); }
personIn();
sleepForOp(windowTime);
personIn();
...
serveNum.get(curFloor).release();
if (type) { acOnly.get(curFloor).release(); }
}
由于三次作业整体架构没有发生改变,每次迭代仅仅是在某些类中增添了一些方法,故均以最终作业的类图分析三次作业架构设计的逐步变化和未来扩展能力。


调度器设计
同时设计了总队列-子队列的模式进行分配,在InputHandler中实例化一个scheduler对象,实现调度器与输入的交互,并将总队列,所有电梯,所有子队列传入了scheduler中,以此实现调度器与其它线程的交互。
调度策略
大体上采用了 LOOK ,具体实现为:
总队列-子队列的分配上,设计了一个简单的权重公式:|方向 * 请求发出楼层 - 方向 * 电梯所在楼层| * 电梯速度。首先将总队列中的请求按权重从大到小排列,然后依次按容量分配给所有电梯,之后不管总队列中是否非空,调度器均 wait,电梯运行时,内部队列与等待队列总人数小于容量时则会唤醒调度器,再次进行分配。
第五次作业任务是需要模拟一个多线程实时电梯系统,系统中存在着6部电梯,可以在1-11层之间运行,主要目标是模拟电梯的上下行,开关门,以及模拟乘客的进出。
第六次作业新增了具有不同参数的新电梯的加入、电梯维护两种请求;
第七次作业新增了电梯可达性以及电梯调度逻辑合理性。
三次作业稳定的内容:
调度策略可以沿用。
三次作业易变的内容:
主要是电梯参数、电梯行为两方面。对我而言,Elevator 类是三次作业中主要修改的部分,包括第六次作业中针对 maintain 指令退请求的设计,第七次作业中针对电梯可达性和服务数目限制的设计。其次是 Scheduler 类,因为可达性的缘故,需要先采用 Dijkstra、Floyd 或者 BFS 等路径规划算法先进行规划,在计算权重,最后再进行分配。