软件构造-实验六-仅供参考
姓名 |
|
学号 |
|
班号 |
|
电子邮件 |
|
手机号码 |
|
目录
1 实验目标概述···· 1
2 实验环境配置···· 1
3 实验过程···· 3
3.1 ADT设计方案··· 3
3.2 Monkey线程的run()的执行流程图···· 10
3.3 至少两种“梯子选择”策略的设计与实现方案···· 13
3.3.1 策略1· 13
3.3.2 策略2· 14
3.3.3 策略3(可选)···· 14
3.4 “猴子生成器”MonkeyGenerator 15
3.5 如何确保threadsafe?···· 17
3.6 系统吞吐率和公平性的度量方案···· 18
3.7 输出方案设计···· 19
3.8 猴子过河模拟器v1· 20
3.8.1 参数如何初始化···· 20
3.8.2 使用Strategy模式为每只猴子随机选择决策策略··· 21
3.9 猴子过河模拟器v2· 22
3.9.1 对比分析:固定其他参数,选择不同的决策策略···· 22
3.9.2 对比分析:变化某个参数,固定其他参数···· 23
3.9.3 分析:吞吐率是否与各参数/决策策略有相关性?···· 26
3.9.4 压力测试结果与分析···· 27
4 实验进度记录···· 28
5 实验过程中遇到的困难与解决途径···· 30
6 实验过程中收获的经验、教训、感想··· 30
6.1 实验过程中收获的经验和教训··· 30
6.2 针对以下方面的感受···· 30
1 实验目标概述
本次实验训练学生的并行编程的基本能力,特别是 Java 多线程编程的能力。 根据一个具体需求,开发两个版本的模拟器,仔细选择保证线程安全(threadsafe) 的构造策略并在代码中加以实现,通过实际数据模拟,测试程序是否是线程安全 的。另外,训练学生如何在 threadsafe 和性能之间寻求较优的折中,为此计算吞 吐率和公平性等性能指标,并做仿真实验。
⚫ Java 多线程编程
⚫ 面向线程安全的 ADT 设计策略选择、文档化
⚫ 模拟仿真实验与对比分析
2 实验环境配置
安装jdk 随意选择目录 只需把默认安装目录 \java 之前的目录修改即可
安装jre→更改→ \java 之前目录和安装 jdk 目录相同即可
安装完JDK后配置环境变量 计算机→属性→高级系统设置→高级→环境变量
系统变量→新建 JAVA_HOME 变量 。
变量值填写jdk的安装目录
系统变量→寻找 Path 变量→编辑
在变量值最后输入 %JAVA_HOME%\bin;%JAVA_HOME%\jre\bin;
系统变量→新建 CLASSPATH 变量
变量值填写 .;%JAVA_HOME%\lib;%JAVA_HOME%\lib\tools.jar(注意最前面有一点)
系统变量配置完毕
3 实验过程
3.1 ADT设计方案
使用strtegy工厂模式,这是策略的接口,方法goaction让猴子选择策略。
策略1
策略2
策略3
Monkey类是描述monkey行为的一个类。
构造方法:
属性:
private final int ID;
private final String direction;
private final int speed;
private int actualspeed = 0;
private Set<Ladder> set = Collections.synchronizedSet(new HashSet<>());
private long starttime = 0;
private List<Integer> list = null;
private int choice = 0;
包含的方法
public void setactualspeed(int speed)
public int getID()
public int getactualspeed()
public String getDirection()
public int getspeed()
public long getbegintime()
public void run()
类Mylog将所有日志都写入一个文件:
Board类是梯子上每一个格子
属性:
private Monkey monkey = null;
private boolean doyouhavamonkey = false;
方法:
public void setmonkey(Monkey monkey)
public boolean getdoyouhavamonkey()
ladder是每一个梯子:
属性:
private final List<Board> ladders = Collections.synchronizedList(new ArrayList<Board>());
private boolean state = false;
private String direction = null;
private int numofmonkey = 0;
private int ID;
private int lastS = 0;
方法:
public Ladder(int ID)
public void setnumofmonkey(int numofmonkey)
public void setState(boolean state)
public void setDirection(String direction)
public void setlastS(int lastS)
public int getlastS()
public int getID()
public int getnumofmonkey()
public boolean getState()
public List<Board> getladders()
public String getDiection()
public boolean startandset()
monkeygenerator类生成猴子和初始化属性并进行运动:
属性:
private int n = 0;
private int h = 0;
private int t = 0;
private static int N = 0;
private int k = 0;
private int MV = 0;
private static int num = 0;
private Set<Ladder> ladders = Collections.synchronizedSet(new HashSet<>());
方法:
public void checkRep()
public void MonkeyGeneratorv1() throws InterruptedException
public void MonkeyGeneratorv2() throws InterruptedException
public void MonkeyGeneratorv3() throws InterruptedException
run类是运行整个程序的开口处:
方法:
public static void main(String[] args)
调用关系如图:
Run |
monkeygenerator |
mylog |
ladder |
board |
monkey |
context |
Strtegy1 |
Strtegy2 |
Strtegy3 |
ladder |
board |
mylog |
mylog |
mylog |
mylog |
mylog |
3.2 Monkey线程的run()的执行流程图
选择策略环节
将猴子放在梯子的第一个格子处完成初始化
ladder.getladders().get(position).setmonkey(this);
通过thread.sleep来实现一秒运动一次
退出环节,如果所处的位置大于20那么就退出循环
在猴子所处的位置上加上速度判断是否超出梯子长度,没有的话那么将猴子放到正确的位置上。并且根据运动的远近设置实际速度。
3.3 至少两种“梯子选择”策略的设计与实现方案
3.3.1 策略1
优先选择没有猴子的梯子,若所有梯子上都有猴子,则优先 选择没有与我对向而行的猴子的梯子;若满足该条件的梯子有很多, 则随机选择;
3.3.2 策略2
优先选择整体推进速度最快的梯子(没有与我对向而行的猴 子、其上的猴子数量最少、梯子上离我距离最近的猴子的真实行进速 度最快)
3.3.3 策略3(可选)
优先选择没有猴子的梯子,若所有梯子上都有猴子,则在岸 边等待,直到某个梯子空闲出来
3.4 “猴子生成器”MonkeyGenerator
主要通过调用Monkey类来生成猴子,然后通过execute来运行进程。策略的选择环节也通过方法参数传送到run方法完成选择。
在v1和v2中,choice分别是
int choice = (int) ((Math.random() * 3) + 1);
int choice = 3;
初始化参数通过在控制台输入数据
运行进程是:
runtime.execute(new Thread(new Monkey(++num, Math.random() > 0.5 ? "R->L" : "L->R",
(int) ((Math.random() * 5) + 1), ladders, list, choice)));
在v3中通过读入文件来初始化参数和生成猴子,有些参数文件中没有,可以自己设定,剩下的需要正则匹配:
从文件中读入初始化n和h:
生成运行猴子进程:
3.5 如何确保threadsafe?
通过在类中设定私有和不可变变量传递保护进程,使用set和list使用被线程保护的类型:
这些变量要么被设定成private让变量不会在其他地方被调用保护了线程的安全。
要么被设定成不可变变量,一旦确定不可以被改变加强了线程的安全。
另一方面我为类中的方法调用加了synchronized (this)的锁,让调用本类的时候同时只能有一个方法在运行,直到它释放锁并且其他方法得到锁的控制权。
3.6 系统吞吐率和公平性的度量方案
吞吐量的计算我统计了猴子的数量N和记录了系统运作的时间T,N/T*1.0就是吞吐率的数值。
公平性的计算:我运用一个list添加了每一只猴子的结束时间,比较每一只猴子的ID,如果小的ID在大的ID后出现就是不公平,反之公平。只用通过两个循环来实现
3.7 输出方案设计
我的输出方案采用了日志的输出方式。
并且写入文件中
3.8 猴子过河模拟器v1
3.8.1 参数如何初始化
通过控制台输入参数初始化参数。
3.8.2 使用Strategy模式为每只猴子选择决策策略
在monkeygenerator中选择策略,如果随机选择策略那么:
int choice = (int) ((Math.random() * 3) + 1);
固定决策策略:
int choice = 3;
随着monkey类的生成将choice传递出去
runtime.execute(new Monkey(num, dieString, speed, ladders, list, choice, time));
调用monkey中的run选择策略:
3.9 猴子过河模拟器v2
3.9.1 对比分析:固定其他参数,选择不同的决策策略
固定的参数如下:
n = 5;
h = 20;
t = 3;
N = 20;
k = 2;
MV = 5;
3.9.2 对比分析:变化某个参数,固定其他参数
变化n的值,固定其他参数,选择策略3.
h = 20;
t = 3;
N = 20;
k = 2;
MV = 5;
变化k,固定其他参数,选择策略3
n = 5;
h = 20;
t = 3;
N = 20;
MV = 5;
变化t,固定其他参数,选择策略3:
n = 5;
h = 20;
t = 3;
N = 20;
k = 10;
MV = 5;
变化N,固定其他参数,选择策略3
n = 5;
h = 20;
t = 1;
k = 10;
MV = 5;
3.9.3 分析:吞吐率是否与各参数/决策策略有相关性?
吞吐量和各个参数和决策策略的确是有关系的。策略3比其他两个策略的吞吐率和公平性高一些。
变化梯子数量的对比性实验在n=2的时候吞吐率和公平性都最高。
变化同时产生猴子的数量,随着k的增加,吞吐率越来越高但是公平性下降的非常明显。这是因为猴子产生数量增加的话,可能ID较大的可能在较小的之前过去。
变化生成猴子的间隔。随着间隔增加,吞吐率和公平性都随之下降。
变化猴子数量。随着猴子越来越多,吞吐率上升的非常之快并且猴子数量越多越稳定,公平性稳定在50%左右。
3.9.4 压力测试结果与分析
大量猴子出现N=500
n = 5;
h = 20;
t = 1;
N = 500;
k = 10;
MV = 5;
猴子速度差异很大,不是1就是5.
n = 5;
h = 20;
t = 1;
N = 20;
k = 10;
MV = 5;
3.10 猴子过河模拟器v3
针对教师提供的三个文本文件,分别进行多次模拟,记录模拟结果。
|
吞吐率 |
公平性 |
Competiton_1.txt |
|
|
第1次模拟 |
0.9584 |
0.9672 |
第2次模拟 |
0.9587 |
0.9894 |
第3次模拟 |
0.9523 |
0.9686 |
第4次模拟 |
0.9575 |
0.9639 |
第5次模拟 |
0.9584 |
0.9478 |
第6次模拟 |
0.9584 |
0.9657 |
第7次模拟 |
0.9653 |
0.9789 |
第8次模拟 |
0.9578 |
0.9658 |
第9次模拟 |
0.9586 |
0.9457 |
第10次模拟 |
0.9586 |
0.9698 |
平均值 |
0.9584 |
0.9663 |
|
|
|
Competiton_2.txt |
|
|
第1次模拟 |
0.9708 |
0.9823 |
第2次模拟 |
0.9723 |
0.9827 |
第3次模拟 |
0.9625 |
0.9856 |
第4次模拟 |
1.02 |
0.9846 |
第5次模拟 |
0.9875 |
0.9896 |
第6次模拟 |
0.9635 |
0.9889 |
第7次模拟 |
0.9875 |
0.9912 |
第8次模拟 |
0.9539 |
0.9845 |
第9次模拟 |
0.9875 |
0.9836 |
第10次模拟 |
0.9658 |
0.9846 |
平均值 |
0.9771 |
0.9858 |
|
|
|
Competiton_3.txt |
|
|
第1次模拟 |
0.8771 |
0.8763 |
第2次模拟 |
0.8773 |
0.8762 |
第3次模拟 |
0.8775 |
0.8793 |
第4次模拟 |
0.8872 |
0.8963 |
第5次模拟 |
0.8770 |
0.8653 |
第6次模拟 |
0.8745 |
0.8745 |
第7次模拟 |
0.8765 |
0.8563 |
第8次模拟 |
0.8736 |
0.8456 |
第9次模拟 |
0.8759 |
0.8778 |
第10次模拟 |
0.8771 |
0.8654 |
平均值 |
0.8774 |
0.8713 |
4 实验进度记录
请使用表格方式记录你的进度情况,以超过半小时的连续编程时间为一行。
每次结束编程时,请向该表格中增加一行。不要事后胡乱填写。
不要嫌烦,该表格可帮助你汇总你在每个任务上付出的时间和精力,发现自己不擅长的任务,后续有意识的弥补。
日期 |
时间段 |
计划任务 |
实际完成情况 |
2019.6.3 |
17:30以后 |
完成v1 |
未完成 |
2019.6.7 |
18:00 |
完成v1 |
进度75% |
2019.6.8-6.14 |
17:00之后 |
完成v1 |
计划完成 |
2019.6.15 |
全天 |
完成v2 |
计划完成 |
2019.6.16 |
上午 |
完成v3 |
计划完成 |
5 实验过程中遇到的困难与解决途径
遇到的难点 |
解决途径 |
不会进程保护
|
查阅资料完成 |
读文件有不同类别的信息
|
控制数量完成 |
6 实验过程中收获的经验、教训、感想
6.1 实验过程中收获的经验和教训
一定要遵守Google的规格,否则代码看起来很乱。将信息保存成private保护线程安全
6.2 针对以下方面的感受
(1) 多线程程序比单线程程序复杂在哪里?你是否能体验到多线程程序在性能方面的改善?
(2) 你采用了什么设计决策来保证threadsafe?如何做到在threadsafe和性能之间很好的折中?
(3) 你在完成本实验过程中是否遇到过线程不安全的情况?你是如何改进的?
(4) 关于本实验的工作量、难度、deadline。
(5) 到此为止你对《软件构造》课程的意见和建议。
1> 多线程要考虑线程安全和线程的竞争情况比单线程考虑的多。但是性能比单线程的性能好
2> 类中方法采用同步,都有线程锁控制,运用局部变量和不可变变量保护线程。 折中的话我只是将重要的方法采用了线程锁,不重要的我都不加。
3> 我在程序过程中遇到过线程不安全。测试的时候遇到过线程锁死,好几只猴子都挤在一起,让线程无法运行。后来发现是没有同步,改进后问题解决。
4> 工作量尚可,难度中上,deadline比较放松。
5> 在软件构造中我收获了很多,从开始我对java一无所知到现在可以写一些小程序。第一次接触面向对象的编程让我感触颇深。遇到问题我也学会了查找资料。