一、多线程的协同与同步控制
多线程编程是Java重要的编程模式,线程经典的生命周期如下:
可通过继承Thread或者实现Runnable接口来实例化一个线程对象,调用其start()方法使其进入就绪状态,等待JVM的调度。进程在获取资源是有几种阻塞状态:
等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。
其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。
三次作业均采用synchronized关键字实现互斥访问共享数据(共享对象)来实现线程同步与通信的。访问共享对象(请求队列)为空,则令线程wait,在请求队列放入请求后,notify通知正处于wait的线程。
二、基于度量的分析
第一次电梯:
复杂度分析:
复杂度方面合格,没有报红,耦合度分析:
类图,展示类属性个数、方法个数以及类的协作关系:
主线程Main创建了输入线程Requests和Elevator,共享对象为RequestQueue,提供了线程互斥的读取和写入方法,经典的生产者消费者模型,结构简单易构架,采用wait-notifyall结构避免轮询浪费时间。在互测环节,没有发现对方的bug,也未被hack,
但事后自己发现了一个bug: 在电梯空闲之时就ctrl+D结束输入线程,电梯线程就会一直处于wait程序无法正常结束,原因在于RequestQueue的get方法没有判断输入线程是否结束就进入wait。
设计原则检查:
Single Responsibility Principle:类及方法功能区别明显单一
Open Close Principle:可扩展性暂未考虑
Liscov Substitution Principle:无继承关系
Interface Segregation Principle:无接口设计
Dependency Inversion Principle:耦合度较低
第二次电梯:
复杂度分析:
可以看出在判断请求队列中是否有捎带人员的方法ev(G)过高,判断捎带的逻辑不够简洁。之后的耦合度分析还比较正常:
类图,展示类属性个数、方法个数以及类的协作关系:
第二次电梯沿用了第一次电梯的架构,增加了捎带方法和判断捎带的条件,调度策略采用类扫描算法,电梯沿一个方向行进时,捎带了所有同方向的乘客,直到到达最远处,不完全的扫描策略导致电梯在转向时,
不能够达到捎带的功能,如果增加转向时捎带估计增加的代码量也不是很大。此次作业未发现别人的bug也未被hack。
设计原则检查:
Single Responsibility Principle:类及方法功能区别明显单一
Open Close Principle:可扩展性暂未考虑
Liscov Substitution Principle:无继承关系
Interface Segregation Principle:无接口设计
Dependency Inversion Principle:耦合度较低
第三次电梯:
复杂度:
依旧是电梯的运行方法、设置捎带与判断捎带复杂度过高,接下来耦合度分析比较正常:
类图,展示类属性个数、方法个数以及类的协作关系:
这次作业没有对之间的设计进行大改,但是只实现了捎带和三部电梯的运行,换乘和请求的拆分没有实现,因此没有进入互测。
设计原则检查:
Single Responsibility Principle:类及方法功能界限有些混乱和模糊
Open Close Principle:可扩展性暂未考虑
Liscov Substitution Principle:无继承关系
Interface Segregation Principle:无接口设计
Dependency Inversion Principle:耦合度较低
三、bug分析与采用的策略
前两次电梯均为发现bug也未被hack,但第三次电梯没有进入互测。采用的策略根据自己调度算法的不足生成相应的测试数据,检查捎带情况,未hack到bug。对于自己的程序有过严重的bug
即电梯空闲时输入CTRL+D之后输入线程结束,但是电梯线程依旧在等待,在前两次电梯没有这个测试点,因此没有发现。在第三次电梯编写过程中,遇到CPU使用时间过长,结果发现是notifyall的
位置不合理,导致线程在获取 synchronized 同步锁失败,同步阻塞时间过长,而这个时间段是占用CPU的。
四、心得体会
在一次完整的软件设计中,应该有完善的需求分析。对一次编程作业也是如此,前期的设计构思非常重要,盲目的开始码代码,边写边构思往往需要推翻一些设计。线程安全方面,需要理清楚不同线程
的共享数据是什么,需不需要加锁,需不需要互斥读写都要仔细考虑,同时根据逻辑的需要判断线程需要等待还是需要通知其他等待的线程。设计原则依旧强调高内聚低耦合,各个类以及实例化的对象要
分工明确、功能明确,这样不仅逻辑上更加清晰明了,对后期维护debug也大有脾益。