Lab 6

1 实验目标概述

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

  • Java多线程编程
  • 面向线程安全的 ADT 设计策略选择、文档化
  • 模拟仿真实验与对比分析

2 实验环境配置

操作系统:macOS Mojave 10.14.5
硬件环境:CPU:Intel Core i7-7920HQ@3.1GHz
RAM:16GB LPDDR3
开发、测试、运行环境:IntelliJ IDEA Ultimate 2019.1.3,Oricle JDK 11.0.3
GitHub Lab_6 URL:unavailable

3 实验过程

3.1 ADT设计方案

ADT只有MonkeyLadder

3.1.1 Monkey

/** Monkey ID. */
private final int id;
/** Direction. */
private final String direction;
/** Velocity. */
private final int velocity;
/** Generated turn. */
private final int startTurn;

对于可以直接阅读的内容,介绍省略。其中startTurn记录了猴子生成的回合(时间),便于后面计算公平度。Monkey是immutable ADT。
getter方法介绍省略;
重载了equalshashCode, toString

3.1.2 Ladder

Ladder是最重要的部分之一,承载了大多核心功能的实现。

/** ID. */
private final int id;
/** Length. */
private final int length;
/** Current access Direction. */
private String direction;
/** Monkeys on current ladder. */
private Monkey[] monkeys;

对于梯子,使用了本身存储方向的设计。
direction代表梯子的方向,没有猴子时设为null,有一只猴子时,方向设置为该猴子的方向。
其中monkeys记录了当前梯子中猴子的位置,数组长度代表梯子长度,null代表当前位置没有猴子。
getter方法省略。
重载了equalshashCode, toString
主要方法如下:

public void addMonkey(final Monkey monkey)// 添加猴子,在monkeys初始位置设置猴子,初始位置根据另外的方法,自动判断方向,计算初始位置,最后根据当前状态更新梯子方向
public void remove(final Monkey monkey)//移除猴子,遍历数组,找到猴子后设置为null,并根据当前状态更新梯子方向
private int getMonkeyNumber()//得到猴子数量,遍历数组即可
public int getPosition(final Monkey monkey) //得到猴子位置,本质是index(数组中位置) + 1
public void move(final Monkey monkey, final int offset)//重要方法,辅助移动猴子,根据偏移量offset,自动判断方向,移动相应位置
private void changeAccess()//辅助方法,自动更换梯子方向,没有猴子时设为null,有一只猴子时,方向设置为该猴子的方向
private int getInitPosition(final Monkey monkey)//得到初始位置,如果方向为L→R则初始位置为1,否则为length
public boolean isAvailable()//判断方法,当没有猴子时,梯子便是available
private boolean hasMonkey(final Monkey monkey)//辅助方法,判断时候存在给定猴子,遍历monkeys数组即可
public int getRealVelocity()//重要方法,计算梯子真实速度,根据方向,计算出最近的两只猴子的距离,并返回后方猴子和距离的最大值,这代表当前新加入的猴子的极限速度
public int indexOf(final Monkey monkey)//重要方法,给出猴子的位置,遍历monkeys数组即可
private void checkRep()//保证每只猴子与梯子速度相同

3.2 Monkey线程的run()的执行流程图

在这里插入图片描述

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

3.3.1 策略1

随意选择所有没有被占用的梯子,如果没有,就随意选择相同方向的梯子。

3.3.2 策略2

在所有同方向或未占用梯子中,选择真实速度最大的梯子。

3.3.3 策略3

在所有同方向或未占用梯子中,选择真实速度大于猴子的梯子中速度最小的梯子,如果没有就选择其中真实速度最大的梯子。具体可见代码内注释。

3.4 “猴子生成器”MonkeyGenerator

根据配置参数,有总数,每隔几轮生成猴子,每次生成几只猴子。
在主方法中有计数器,用取余数的方法每隔几轮就可以调用一次生成方法,记录已生成猴子数量,判断是否当前再生成每轮猴子数量后超过总数,如果是,则计算余数,循环该次数,用给定参数和随机数生成猴子,否则循环每轮猴子数量次数。
生成猴子本身只涉及到参数和构造器,由于简单无需介绍。

3.5 如何确保threadsafe?

Cross线程中,首先在run()中将该线程设为阻塞状态,给当前线程上锁,在suspendtrue的情况下,直到在主方法中对每个线程都解除阻塞,解除阻塞就是在相应线程中将suspend解除。接下来对主类中的LADDERS上同步锁,防止运行过程中其他线程同时修改造成错误。
在每个猴子移动过程中,参考的状态均是上一回合结束后的状态,使用status保存该状态,这样就确保了猴子不会采用上帝视角观察其他猴子的行为。如果多个猴子选择一个梯子,则在一个猴子选上该梯子后,其他猴子均不能再进行选择,只能在下一轮再次选择。

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

对于吞吐率,计算非常简单,只需将总数/总的回合数即可;
对于公平性,在生成每个猴子时记录当前回合数,猴子到对岸时记录总用时,单独存储到另外的status中。使用参考公式计算,用两次for循环,可以容易地算出公平度。

3.7 输出方案设计

对于日志,使用了Lab4中的方案,直接套用即可,在每次线程进行操作时,记录操作和状态。存储在application的log.log中。
GUI部分,对于每一轮,先使用line画出梯子(位置由相应参数自动计算),接下来每次画出status中保存的猴子信息,在相应位置用string画出猴子ID。每一轮都repaint即可。结束时,最终用JOptionPane.showMessageDialog给出配置参数、吞吐率和公平度。

3.8 猴子过河模拟器v1

3.8.1 参数如何初始化

使用了读取配置文件的方式,初始化6个基本参数。文件读取方法使用了Lab3到Lab5中的方法,每次都用正则表达式进行分析,提取参数。

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

在线程Cross中直接使用了3个策略方法,没有使用Strategy design pattern,因为这样做反而不便,首先是策略数量不是很多,其次,使用策略需要获取猴子的信息,这样还需要额外的参数,反而使得程序的可读性和内聚度降低。

3.9 猴子过河模拟器v2

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

3.9.1 对比分析:固定其他参数,选择不同的决策策略

使用如下参数测试:

n = 5
h = 20
t = 2
N = 100
k = 5
MV = 10
3.9.1.1 策略1
吞吐率1.56251.47061.69491.53851.6949
公平性0.50140.64440.50100.28400.3572

在这里插入图片描述

3.9.1.2 策略2
吞吐率1.56251.47061.63931.58731.7857
公平性0.35560.48570.62830.53450.6251

在这里插入图片描述

3.9.1.3 策略3
吞吐率1.63931.69491.53851.66671.5625
公平性0.53660.56650.53580.36610.6679

在这里插入图片描述
总对比:
在这里插入图片描述

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

标准参数:

n = 10
h = 20
t = 2
N = 100
k = 10
MV = 10
3.9.2.1 n
n吞吐率公平性
20.87720.8917
41.51520.8428
62.27270.6590
82.38100.7325
102.50000.6263
3.9.2.2 t
t吞吐率公平性
12.63160.8521
22.56410.7131
32.04080.6267
42.00000.3984
51.49250.3370
3.9.2.3 N
N吞吐率公平性
1002.50000.4262
2003.38980.4206
3003.79750.4382
4004.08160.2748
5004.13220.2387
3.9.2.4 k
k吞吐率公平性
51.69490.4352
102.56410.6646
152.70270.7463
202.63160.8598
252.70270.9236
3.9.2.5 MV
MV吞吐率公平性
2 1.92310.7063
42.43900.6804
62.56410.6772
82.77780.5980
102.43900.6432

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

根据测试结果,显然可以发现其中的规律性:例如梯子数增大,则吞吐率增加,公平性降低;产生间隔增大时,吞吐率降低,公平性下降;猴子总数增加,吞吐率随之增大而公平性下降;每次产生猴子数增加,则吞吐率在猴子数较少时增加,通行能力饱和时,吞吐率不再发生明显变化,而公平性随之提升;最大速度增加时,吞吐率随之增加而公平性略有下降。

3.9.4 压力测试结果与分析

3.9.4.1 高密度

标准参数:

n = 5
h = 20
t = 1
N = 100
k = 50
MV = 10
吞吐率1.56251.47061.63931.53851.5385
公平性0.99390.99560.99350.99270.9952
3.9.4.2 高速度差异

|–|--|–|--|–|--|

吞吐率1.56251.47061.63931.53851.5385
公平性0.99390.99560.99350.99270.9952

3.10 猴子过河模拟器v3

针对教师提供的三个文本文件,分别进行多次模拟,记录模拟结果。

吞吐率公平性
Competiton_1.txt
第1次模拟2.08330.7856
第2次模拟2.14290.8001
第3次模拟2.09790.7996
第4次模拟2.09790.7924
第5次模拟2.05480.8108
第6次模拟2.06900.8080
第7次模拟2.05480.8109
第8次模拟2.11270.7868
第9次模拟2.04080.8257
第10次模拟2.06900.8055
平均值2.08230.8025
Competiton_2.txt
第1次模拟4.09840.7999
第2次模拟4.23730.7970
第3次模拟4.20170.7976
第4次模拟4.06500.7933
第5次模拟4.34780.7803
第6次模拟4.09840.7964
第7次模拟4.20170.8107
第8次模拟4.27350.7776
第9次模拟4.16670.8007
第10次模拟4.13220.7930
平均值4.18230.7947
Competiton_3.txt
第1次模拟1.04170.8008
第2次模拟1.04170.7754
第3次模拟1.05260.7701
第4次模拟1.06380.7895
第5次模拟1.04170.8044
第6次模拟1.06380.7943
第7次模拟1.04170.8004
第8次模拟1.00000.7612
第9次模拟1.04170.7984
第10次模拟1.06380.7952
平均值1.04530.7890

4 实验过程中遇到的困难与解决途径

遇到的难点解决途径
多线程的建立与同步网络搜索与学习
猴子移动的具体细节存在争议与其他人讨论

6 实验过程中收获的经验、教训、感想

6.1 实验过程中收获的经验和教训

多线程的debug是比较困难的。需要控制好线程同步,并且在此基础上实现具体功能,如果出现了问题需要从底层到上层依此排查,反复测试。有时,在较为简单的情况下,发现不了一些隐藏较深的bug,需要在实际测试过程中设置一些极限情况,便于发现问题。

6.2 针对以下方面的感受

  1. 多线程程序比单线程程序复杂在哪里?你是否能体验到多线程程序在性能方面的改善?
    多线程可以模拟每个物体的一系列动作并能够与其他线程进行交流。复杂之处主要在于它的同步,需要具备一定的执行次序和规则。该实验似乎并不能够体现多线程的性能改善。
  2. 你采用了什么设计决策来保证threadsafe?如何做到在threadsafe和性能之间很好的折中?
    在每次循环时一次激活线程,并在每个线程执行过程中给所有梯子上锁。
  3. 你在完成本实验过程中是否遇到过线程不安全的情况?你是如何改进的?
    没有遇到。
  4. 关于本实验的工作量、难度、deadline。
    工作量相比lab3以后的实验明显减小,难度适中,但是需要深入一些细节。
  5. 到此为止你对《软件构造》课程的意见和建议。
    对Java进行了较为系统的教学,除了网络部分几乎完整覆盖了。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值