HIT 软件构造 Lab6

实验问题解决及经验总结:

本次实验的任务是每隔一段时间,同时产生一定数量的猴子线程,各自完成过河任务。
在本次实验过程中,实现多线程任务主要运用了以下四点新知识:
1.通过重写Runnable接口,重写run函数完成猴子线程的运行;
2.通过线程池完成多线程的启动,关闭管理。
ExecutorService exe = Executors.newFixedThreadPool(monkeyGroup.size());
exe.shutdown()会在所有线程结束后返回
再通过exe.isTerminated()方法判断是否为正常终止,正常终止返回true,作为判断所有猴子线程顺利过河的判断条件;
在这里插入图片描述
3.猴子的“定时出生”:同时启动所有猴子线程后,直接通过Thread.sleep()方法等待一定时间后在出生点“出生”(即开始执行下一步)。其实本来可以使用具有周期性调度方法的ScheduleExecutorService,这里小小地偷了个懒?。。
4.竞争、死锁问题:由于猴子没有上帝视角,所以猴子的行动有滞后性,而不会超越前面的猴子产生奔跑时的竞争。所以这里只对猴子上梯子时上了锁
(此处要注意,上了梯子之后要在掌握着锁的情况下往前跳一步,不然后面的猴子就会被停在入口处的猴子挡住了。。。(理论上锁住了,要排队影响效率,但是实验过程中发现是直接被锁死了。。 ))
在这里插入图片描述5.单位时间的实现:这里实现的有些随意~,没有使用系统时钟,直接通过sleep(),每一步停顿1秒。但是好处是可以直接通过调节MonkeyThread.SECOND的大小,加快流速,就算实现之初,吞吐率低下,也不会等个半天啦!

实验报告

1. 实验目标概述
本次实验训练学生的并行编程的基本能力,特别是 Java 多线程编程的能力。
根据一个具体需求,开发两个版本的模拟器,仔细选择保证线程安全(threadsafe)
的构造策略并在代码中加以实现,通过实际数据模拟,测试程序是否是线程安全的。另外,训练学生如何在 threadsafe 和性能之间寻求较优的折中,为此计算吞吐率和公平性等性能指标,并做仿真实验。
⚫ Java 多线程编程
⚫ 面向线程安全的 ADT 设计策略选择、文档化
⚫ 模拟仿真实验与对比分析

2 实验环境配置

3 实验过程

3.1 ADT设计方案

设计了哪些ADT、各自的作用、属性、方法;
给出每个ADT的specification;
Monkey
变量名 类型 说明
birthtime Int(final) 猴子的出生时间
id int(final) 猴子的编号
speed int 猴子的速度
direction int(final) 前进的方向
lifetime Int 出生至今所经历的时间
方法:
对内部属性的get方法,同时,对于speed和lifetime分别有下列方法:
方法 作用
public void increaseLifetime() 增加诞生后的生命长度,每次加1
public void setSpeed(int speed) 调整速度
specification

MonkeyThread
属性
变量名 类型 说明
monkey Monkey 猴子对象,包含猴子的编号、出生时间、速度大小、方向、诞生至今的时间等信息
ladder Ladder 梯子对象,调用其中的方法,完成前进、上梯子的动作
ladderChooseStrategies List 可选的上梯子策略列表
ladderChooseStrategy LadderChooseStrategy 当前猴子线程所选择的上梯子方法
SECOND long 时间单位,抽象为秒
log Log 日志
paint PaintRiverCrossing 用于添加特定的画布
方法:实现所继承的接口Runnable的run()方法,实现上梯子、前进等操作,直至完成过河动作。
Specification

MonkeyGenerator
变量名 类型 说明
monkeyGroup List 所待产生的猴子集合
log Log 日志

方法 作用
public Monkey creatSingleMonkey(String infor) 根据特定格式的字符串,产生相应的猴子对象
public List startThreads(Ladder ladder, PaintRiverCrossing paint) 启动线程
specification

Ladder
变量名 类型 说明
ladder Int型数组 梯子(0表示该踏板位置没有猴子,1表示该踏板上猴子从左向右移动,2表示该猴子从右向左移动)
n int (final类型) 梯子的数目
h int(final类型) 梯子的踏板数

方法 作用
public boolean upStairs(int ladderNum, Monkey monkey) 上梯子(此处要注意线程安全,在多线程并发时,需要锁住)
public int goForward(int from, int ladderNum, Monkey monkey) 前进(根据前面是否有猴子“挡路”分两种情况:1.按照原速度前进,不会越过现在前面所在的位置;2.按照原速度前进,会越过现在前面所在的位置)
public int getEmptyLadder() 获取空梯子的编号(若不存在空梯子,返回-1)
public boolean isSameDirection(int n, int dire) 判断梯子上当前猴子的前进方向是否与准备上梯子上的猴子运行方向一致
public int getNumOfMonkeysOnLadder(int num) 返回梯子上猴子的数目
public int getDistanceOfTheFront(int num, int pos) 获得当前梯子上距离最近的猴子的距离
public int getPosStatus(int i,int j) 获取某踏板上是否有猴子,且返回的值包含猴子的方向信息
public void setPosStatus(int direc,int i,int j) 设置某处梯子的状态(若有猴子上去,那么该处的值改为猴子的方向)
public int getNum() 获取梯子的数目
public int getSteps() 获取梯子上的踏板数
Specification

(可选)以类图形式给出多个类之间的关系。
3.2 Monkey线程的run()的执行流程图

3.3 至少两种“梯子选择”策略的设计与实现方案

3.3.1 策略1

优先选择没有猴子的梯子,若所有梯子上都有猴子,则优先选择没有与我对向而行的猴子的梯子;若满足该条件的梯子有很多,选择当前梯子上最后一只猴子距离我最远的梯子,若还是相同,则随机选择;

3.3.2 策略2

优先选择没有猴子的梯子,若所有梯子上都有猴子,则在岸边等待,直到某个梯子空闲出来。

3.3.3 策略3(可选)

优先选择整体推进速度最快的梯子(没有与我对向而行的猴子、梯子上离我距离最近的猴子的真实行进速度最快、梯子上的猴子数量最少);

3.4 “猴子生成器”MonkeyGenerator

如何设计和实现。
在启动线程前,初始化N只猴子待用,同时根据k,t对猴子的出生时间进行初始化。其中方向、速度均用Random对象的nextInt(n)方法产生。
启动线程则直接根据之前生成的List,中的Monkey对象的产生时间Thread.sleep()相应的时间后进行之后的选梯子、上梯子动作。
多线程起始和终止的时间点通过线程池控制:
ExecutorService exe = Executors.newFixedThreadPool(monkeyGroup.size());
最后不要忘记在所有线程终止后将线程池关闭exe.shutdown();
核心代码如下:

3.5 如何确保threadsafe?

这里线程安全主要是猴子上梯子时,多线程并发,产生竞争所导致的。因此只要在选梯子时各个猴子进行“排队”即可。即,将各个猴子线程用一个公共的锁锁住,轮流选梯子。
核心代码如下:

3.6 系统吞吐率和公平性的度量方案

在启动线程的猴子生成器中,用一个List保存所有过河后猴子的状态:出生时间,从出生到过河所花费的时间。在所要线程都完成过河任务之后,对List进行统计。
公平性:
依次比较任意两只未进行判断猴子是否有如下情况成立。成立则加1,否则减1.
(birth1 - birth2) * ((lifetime1 + birth1) - (lifetime2 + birth2)) >= 0

吞吐率
遍历上述记录过河后猴子信息的List,得出最大的birth + lifetime ,即为所有猴子过河所需的时间。
再通过公式Th=N/T计算吞吐率。
3.7 输出方案设计

日志
在MonkeyGenerator中放置一个Logger对象
Logger log = Logger.getLogger(“MonkeysCrossRiver”);
再在启动每个猴子线程时,传入参数log,并注意,在每个线程中必须对log添加的每个FilerHandler对象中的file名进行变化,这里使用了猴子编号
GUI
这里定义了一个src/Paint/PaintRiverCrossing.java类,通过在JFrame中添加JPanel画布实现猴子过河界面的呈现。
其中,梯子用方格表示,猴子的运动方向用1、2表示,1表示梯子的当前踏板上有猴子,且猴子的运动方向为从左到右,若为2,则表示当前踏板上猴子的运动方向为从右到左。如下图为其中的一个过程截图:
在这里插入图片描述

可视化(可选)

3.8 猴子过河模拟对象器v1
3.8.1 参数如何初始

在生成MonkeyGenerator对象时,就按照参数生成相应的N只猴子,此处循环变量为i。猴子的编号从1开始,生成至N,出生时间则为t*i, 方向与速度通过调用Random对象的nextInt(n)方法生成,其中1表示猴子从左到右移动,2表示从右往左移动。
速度的最大值由,nextInt(MV)中的参数控制。

3.8.2 使用Strategy模式为每只猴子选择决策策略

这里定义了一个LadderChooseStrategy抽象类,定义了一个成员变量clock,声明了一个抽象方法public abstract class LadderChooseStrategy。
再由两种具体策略类继承该抽象类分别实现该方法。
EmptyOrAgainstStrategy
EmptyStrategy
将所有可用策略存放在一个List中,在线程中进行梯子策略选择时,使用Random进行任意选取。
具体代码实现如下:在这里插入图片描述

运行截图如下:
在这里插入图片描述
3.9 猴子过河模拟器v2

在不同参数设置和不同“梯子选择”模式下的“吞吐率”和“公平性”实验结果及其对比分析。
3.9.1 对比分析:

固定其他参数,选择不同的决策策略
此处由于策略2吞吐率过小,没法显示。

参数 n h t N k MV
取值 1-10 20 5 100 5 5
在这里插入图片描述
在这里插入图片描述

3.9.2 对比分析:变化某个参数,固定其他参数

Exp1:n不同
参数 n h t N k MV
取值 1-10 20 5 100 5 5

在这里插入图片描述在这里插入图片描述

Exp2:t不同
参数 n h t N k MV
取值 5 20 1-5 100 5 5
在这里插入图片描述在这里插入图片描述

Exp3:N不同
参数 n h t N k MV
取值 5 20 2 100,300,500,700,900 5 5
在这里插入图片描述在这里插入图片描述

Exp4:k不同
参数 n h t N k MV
取值 10 20 2 100 10,20,30,40,50 5
在这里插入图片描述在这里插入图片描述

Exp5:MV不同
参数 n h t N k MV
取值 10 20 2 100 10 5-9
在这里插入图片描述在这里插入图片描述

3.9.3 分析:吞吐率是否与各参数/决策策略有相关性?

1)策略1与策略3受各参数影响不大,主要受N的影响。其他参数不变,N越大,吞吐率越小。但是在梯子数目足够多,产生的猴子数目比较密集的情况下,策略1的吞吐率会在增大。
2)策略2因为只能单只猴子上梯子的缘故,受各参数影响较大,尤其是受MV影响较大。其他策略受MV的影响较小。
3)当n为1-2时,所有策略的吞吐都急剧减小。
4)吞吐率其决定性作用的还是选择策略,在所有决策策略中,第1种吞吐率情况最佳。
3.9.4 压力测试结果与分析

压力测试 1:设计一种参数配置,使得产生的猴子数量非常多、非常密集,
而梯子数量有限。
参数 n h t N k MV
取值 10 20 2 1000 100 5

猴子过河的吞吐率为:32.25806451612903
猴子过河的公平性为:0.5495255255255255
分析:这里使用的是第一种选择策略,发现当产生的猴子非常密集时,反倒大大提高了吞吐率。

压力测试 2:设计一种参数配置,使得各猴子的速度差异非常大。

参数 n h t N k MV
取值 5 20 2 100 10 5
策略1:
猴子过河的吞吐率为:3.225806451612903
猴子过河的公平性为:0.5838383838383838
各猴子速度差异对吞吐率的影响并不大,只是稍微加快了一点。
3.10 猴子过河模拟器v3

针对教师提供的三个文本文件,分别进行多次模拟,记录模拟结果。
吞吐率 公平性
Competiton_1.txt
第1次模拟 4.411764705882353 0.8423188405797102
第2次模拟 4.109589041095891 0.85520624303233
第3次模拟 4.285714285714286 0.8483389074693423
第4次模拟 4.3478260869565215 0.8485172798216276
第5次模拟 3.2195370091145093E-4 0.8508361204013378
第6次模拟 3.75 0.83041248606466
第7次模拟 1.07035668566193E-4 0.8370122630992196
第8次模拟 4.285714285714286 0.8581939799331104
第9次模拟 3.9473684210526314 0.8125752508361204
第10次模拟 0.0012495886770604676 0.8623411371237458
平均值 2.9139655 0.8445753

Competiton_2.txt 吞吐率 公平性
第1次模拟 8.771929824561404 0.8514789579158316
第2次模拟 8.771929824561404 0.8664208416833667
第3次模拟 2.293577981651376 0.5295711422845691
第4次模拟 6.756756756756757 0.8609859719438878
第5次模拟 5.813953488372093 0.83025250501002
第6次模拟 8.19672131147541 0.8401603206412825
第7次模拟 8.771929824561404 0.864561122244489
第8次模拟 8.771929824561404 0.866436873747495
第9次模拟 8.771929824561404 0.8616753507014028
第10次模拟 4.032258064516129 0.5481362725450902
平均值 6.218099 0.7919679

Competiton_3.txt 吞吐率 公平性
第1次模拟 1.8518518518518519 0.7264646464646465
第2次模拟 0.0011478157067101307 0.6468686868686868
第3次模拟 1.6129032258064515 0.686060606060606
第4次模拟 1.3513513513513513 0.6993939393939393
第5次模拟 1.4705882352941178 0.6937373737373738
第6次模拟 1.5873015873015872 0.7333333333333333
第7次模拟 1.694915254237288 0.6488888888888888
第8次模拟 1.2987012987012987 0.705050505050505
第9次模拟 3.7611659614480487E-4 0.6129292929292929
第10次模拟 1.3333333333333333 0.6642424242424242
平均值 1.220247 0.681697

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值