实验简介
利用多线程开发一个用于模拟猴子过河的程序
需求描述
有一条河,河面上有?架同样的梯子,每个梯子长度为ℎ,意即有ℎ条均匀分 布的踏板。 河的左岸有一群猴子,右岸也有一群猴子。左岸的猴子想到右岸,右岸的猴 子想到左岸。
一只猴子在某时刻选择并爬上某个梯子,意味着它从其“出生地”跳到了该 梯子在猴子所在一侧的第 1 个踏板上。猴子一旦上了某个梯子,就不能在中途跳 到别的梯子上。 梯子太窄,一只猴子无法越过在其前方同向行进的其他猴子,只能跟随其后 (意即:只有在其前方的猴子向前行进腾出了空间,该猴子才能向前进)。猴子 无法越过在其前方的对向行进的猴子,也无法在梯子上后退。若在同一架梯子上 有两只对向行进的猴子相遇,则此时产生了“死锁”,过河失败。 每个猴子过河的速度不同,其速度?定义为每秒钟可爬过的踏板的数量。在 独占一部梯子过河的情况下,一只速度为?的猴子过河所需的时间为 秒。如果有 另一只猴子在它前方但速度比它慢,则它的行进速度不得不降低。如果ℎ ≤ ?, 在猴子跳上梯子且前方没有其他猴子阻挡的情况下,其过河时间为 1 秒。 例 1:在某个时刻,猴子?位于某架梯子的第 1 个踏板上,其速度为 3,猴 子?位于同一架梯子的第 3 个踏板上,其速度为 1。假如此时?线程在做行动 决策,在它独自过河的情况下(理想情况),下一秒它应跳到第1 + 3 = 4个踏板 上,但是,它观察到自己前方?的存在,第 3 个踏板目前由?拥有,故?无法 按预期跳到第 4 个踏板上,它只能降低速度,以速度 1 跳到第 2 个踏板上。
有人问:“?也在向前行进,下 1 秒钟?应该移动到第 4 个踏板上,所以?可以提前做好预判,跳到?空出的第 3 个踏板上。”——本实验要求“不能使用 上帝视角”——猴子只能观察各梯子和各猴子的当前状态及其变化,但不能得知 其他任何猴子所采取的行动策略。所以,?做决策的时候,不能获知?的行动 策略。 例 2:假如?此时在第 2 个踏板上,它的决策只能是“原地不动”。到了下 一次做决策的时候,除非?已经空出了第 3 条踏板,否则它还是不能行动。
猴子过河模拟器 v1
开发一个模拟猴子过河的仿真程序:
- 初始化参数: ? = 1~10,ℎ = 20, ? = 1~5,? = 2~1000, ? = 1~50, ?? = 5~10,各参数详细说明见后续描述。可由模拟器的用户在命令 行或 GUI 输入具体取值,也可由程序从某个外部配置文件中读取参数 值(该方法可以支持多次模拟,每次模拟使用不同的参数值配置,均 在配置文件里定义,故建议采用)
- 设计 ADT:猴子 Monkey、梯子 Ladder、以及其他必要的 ADT。 (3) 开发“猴子生成器”MonkeyGenerator:每隔?秒钟同时产生?个 Monkey 对象(例如:第 0 秒生成?个 Monkey 对象,第?秒又同时产生 ?个 Monkey 对象,第 2?秒…)
- 并为各只猴子生成以下属性:
(1) 名字 ID(int):按照产生的时间次序进行自然数编号,同一时刻 同时生成的猴子的 ID 应有不同的编号
(2) 方向 direction(String):值随机指定,左岸到右岸(“L->R”), 或者从右岸到左岸(“R->L”)
(3) 速度?:正整数,取值范围为[1,??],??为最大可能的速度。 如果 不为整数,则最后一次产生的猴子个数为?%? - 启动过河线程:针对生成的每个 Monkey 对象,为其生成一个独立的 线程,其目标是通过某个 Ladder 对象实现“过河”的目标;
- 设计并实现多种梯子选择策略:?个 Ladder 对象是在所有猴子的线 程之间共享的数据对象,任何 Monkey对象被产生出来之后,均可观 察到所有 Ladder 对象的当前状态,并根据某种决策策略选择某一架梯子向对岸行进,或者在河岸保持原地观察(暂时不选择具体梯子)。 “选择某架梯子”是指:若该梯子靠近该猴子的第 1 个踏板上没有猴子,则猴子跳上该踏板;否则,在原地等待,直到所选梯子的第 1 个 踏板空出来才可以跳上去。跳上某梯子的第 1 个踏板不需要耗费时间(即不考虑从猴子出生地到梯子的跳跃所需的时间)
首先对于需求进行分析,粗略的估计一下我们需要一个主线程:用来存放梯子的数据、初始化的数值、以及猴子模拟器的处理。都放在主线程当中。然后除了具体的ADT 以外,还需要一个用来处理猴子的动作的线程。
Direction类
顾名思义,就是方向的类,但是注意的是这个方向不仅仅是猴子的运行方向也是一个梯子的运行方向——指的是当前的这个梯子上的猴子的运行的方向。
package lab;
/**
* The direction of the monkeys and the ladders.
* @author 59841
*/
public enum Direction {
neutral,left,right
}
Ladder类
ladder类,就是猴子用来过河的工具,它有两个很重要的成员变量就是direction和rungs。direction就是梯子当前的猴子的运动方向,主要是为了避免死锁,初始化为中间,也就是说这是一个空的梯子。猴子只会登上一个空的梯子或者运行方向和自己一致的梯子,其中当猴子登上一个空的梯子的时候就会把梯子的运行方向调整为和自己一致。rungs就是一个横杆的动态数组。
package lab;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Ladder {
private final int ladderID;
private Direction direction = Direction.neutral;
private final List<Rung> rungs = Collections.synchronizedList(new ArrayList<Rung>());
/**
* ladder class.
* 3 variables.
* mutable class.
* REP: null
* AF:
* ladderID: mark the ID of the ladders.
* Direction: the current direction of the monkeys one this ladder.
* rungs: includes all the rungs of the ladder.
* RI:ladderID is final.
* (done)
*/
public Ladder(int ladderID, int ladderLength) {
this.ladderID = ladderID;
for (int i = 0; i < ladderLength; i++) {
Rung newRung = new Rung(i);
rungs.add(newRung);
}
}
public Direction getDirection() {
return direction;
}
public void setDirection(Direction direction) {
this.direction = direction;
}
public List<Rung> getRungs() {
return rungs;
}
public int getLadderID() {
return ladderID;
}
}
Rung类
Rung类就是,横杆类,代表椅子上的横杆布尔型isTaken用来表示这个横杆是否被占用,monkey表示这个横杆上的猴子
package lab;
public class Rung {
private final int rungID;
private boolean isTaken;
private Monkey monkey;
/**
* class Rung
* mutable class.
* AF: the rungs of a ladder.
* RI: ID is final
* (done)
*/
public Rung(int rungID) {
this.rungID = rungID;
this.isTaken = false;
}
public synchronized boolean isTaken() {
return isTaken;
}
public synchronized void setTaken() {
this.isTaken = true;
}
public synchronized void unTake() {
this.isTaken = false;
}
public synchronized Monkey getMonkey() {
return monkey;
}
public synchronized void setMonkey(Monkey monkey) {
this.monkey = monkey;
}
public synchronized int getRungID() {
return rungID;
}
}
Monkey类
没什么内容就是三个变量代表猴子的序号、速度和方向,值得注意的一点是这个类是一个不可变类。
package lab;
public class Monkey {
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + id;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Monkey other = (Monkey) obj;
if (id != other.id) {
return false;
}
return true;
}
private final int id;
private final int velocity;
private final Direction direction;
/**
* monkey class.
* Immutable class.
* REP: null
* AF:
* ID: mark the sequence of the monkey.
* velocity: the speed.
* Direction: the moving direction.
* (done)
*/
public Monkey(int id, int velocity, Direction direction) {
this.id = id;
this.velocity = velocity;
this.direction = direction;
}
public int getVelocity() {
return velocity;
}
public Direction getDirection() {
return direction;
}
public int getID() {
return id;
}
}
MonkeyGenerator类
猴子生成器类,具体成员变量(同名的成员变量下文会出现多次不再赘述)
- timeInteval 表示生成猴子的时间间隔
- oneTimeMkyNumber 表示一次性生产猴子的数量
- mkyNumber 一共生产的猴子的数量
- maxVelocity 生产猴子的最大数量
- toLeftMonkeyList 右岸正在等待的猴子数组
- toRightMonkeyList 左岸正在等待的猴子数组
- allMonkeyThreads 猴子线程的数组(下文会说到)
- monkeyInfo 用来存储猴子的产生时间(在v3中才会用到)
- ladders 梯子的数组
- line 输入的文本信息(在v3中才会用到)
- hasStarted 是否已经开始
- start 记录程序执行时间
- timeDuration 记录产生猴子的时间(在v3中才会用到)
值得注意的是,这里的ladders初始化是空的,也就是这个ladders实际不在猴子生成器中产生,而是在一开始构造的时候进行赋值。init()函数就是实现随机生成猴子的,逻辑很简单循环生成一次生成的猴子,不足一次生成数的时候,将剩余的猴子生成完。并且,所有的猴子都不是直接的前文的猴子类而是猴子线程类。在生成了猴子线程类以后,要将猴子添加到对应的数组当中。init1()在后文v3中使用。
package lab;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
public class MonkeyGenerator {
private int timeInteval;
private int oneTimeMkyNumber;
private int mkyNumber;
private int maxVelocity;
private final ArrayList<Monkey> toLeftMonkeyList = new ArrayList<>();
private final ArrayList<Monkey> toRightMonkeyList = new ArrayList<>();
private final ArrayList<MonkeyThread> allMonkeyThreads = new ArrayList<>();
private final Map<MonkeyThread, Integer> monkeyInfo = new HashMap<>();
private ArrayList<Ladder> ladders;
private ArrayList<String> line;
private boolean hasStarted = false;
private long start;
private int timeDuration;
/**Class builder, used to build a monkeyThread and a mainThread.
* 13 variables included.
* Mutable class.
* REP: null
* AF:
* timeInteval: control the speed of creating monkeys.
* oneTimeMkyNumber: the number of the new monkeys created one time.
* mkyNumber: the number of all the monkeys.
* maxVelocity: the max speed of the monkeys.
* All the lists are used to record the thread infomation.
* RI: the first 6 variables are final, can't be changed.
* (done)
*/
public long getStart() {
return start;
}
public ArrayList<Monkey> getToLeftMonkeyList() {
return toLeftMonkeyList;
}
public ArrayList<Monkey> getToRightMonkeyList() {
return toRightMonkeyList;
}
public ArrayList<String> getLine() {
return line;
}
/**
* builder a monkey generator.
* @param timeInteval
*
* @param oneTimeMkyNumber
*
* @param mkyNumber
*
* @param maxVelocity
*
*/
public MonkeyGenerator(
int timeInteval, int oneTimeMkyNumber,
int mkyNumber,int maxVelocity) {
this.timeInteval = timeInteval;
this.oneTimeMkyNumber = oneTimeMkyNumber;
this.mkyNumber = m