利用多线程开发一个用于模拟猴子过河的程序
有一条河,河面上有?架同样的梯子,每个梯子长度为ℎ,意即有ℎ条均匀分 布的踏板。 河的左岸有一群猴子,右岸也有一群猴子。左岸的猴子想到右岸,右岸的猴 子想到左岸。
一只猴子在某时刻选择并爬上某个梯子,意味着它从其“出生地”跳到了该 梯子在猴子所在一侧的第 1 个踏板上。猴子一旦上了某个梯子,就不能在中途跳 到别的梯子上。 梯子太窄,一只猴子无法越过在其前方同向行进的其他猴子,只能跟随其后 (意即:只有在其前方的猴子向前行进腾出了空间,该猴子才能向前进)。猴子 无法越过在其前方的对向行进的猴子,也无法在梯子上后退。若在同一架梯子上 有两只对向行进的猴子相遇,则此时产生了“死锁”,过河失败。 每个猴子过河的速度不同,其速度?定义为每秒钟可爬过的踏板的数量。在 独占一部梯子过河的情况下,一只速度为?的猴子过河所需的时间为 秒。如果有 另一只猴子在它前方但速度比它慢,则它的行进速度不得不降低。如果ℎ ≤ ?, 在猴子跳上梯子且前方没有其他猴子阻挡的情况下,其过河时间为 1 秒。 例 1:在某个时刻,猴子?位于某架梯子的第 1 个踏板上,其速度为 3,猴 子?位于同一架梯子的第 3 个踏板上,其速度为 1。假如此时?线程在做行动 决策,在它独自过河的情况下(理想情况),下一秒它应跳到第1 + 3 = 4个踏板 上,但是,它观察到自己前方?的存在,第 3 个踏板目前由?拥有,故?无法 按预期跳到第 4 个踏板上,它只能降低速度,以速度 1 跳到第 2 个踏板上。
有人问:“?也在向前行进,下 1 秒钟?应该移动到第 4 个踏板上,所以?可以提前做好预判,跳到?空出的第 3 个踏板上。”——本实验要求“不能使用 上帝视角”——猴子只能观察各梯子和各猴子的当前状态及其变化,但不能得知 其他任何猴子所采取的行动策略。所以,?做决策的时候,不能获知?的行动 策略。 例 2:假如?此时在第 2 个踏板上,它的决策只能是“原地不动”。到了下 一次做决策的时候,除非?已经空出了第 3 条踏板,否则它还是不能行动。
开发一个模拟猴子过河的仿真程序:
首先对于需求进行分析,粗略的估计一下我们需要一个主线程:用来存放梯子的数据、初始化的数值、以及猴子模拟器的处理。都放在主线程当中。然后除了具体的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 rungs = Collections.synchronizedList(new ArrayList());
/**
* 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 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类
猴子生成器类,具体成员变量(同名的成员变量下文会出现多次不再赘述)
值得注意的是,这里的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 toLeftMonkeyList = new ArrayList<>();
private final ArrayList toRightMonkeyList = new ArrayList<>();
private final ArrayList allMonkeyThreads = new ArrayList<>();
private final Map monkeyInfo = new HashMap<>();
private ArrayList ladders;
private ArrayList 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 getToLeftMonkeyList() {
return toLeftMonkeyList;
}
public ArrayList getToRightMonkeyList() {
return toRightMonkeyList;
}
public ArrayList 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 = mkyNumber;
this.maxVelocity = maxVelocity;
start = System.currentTimeMillis();
}
public MonkeyGenerator(ArrayList line) {
this.line = line;
start = System.currentTimeMillis();
}
/**
* Create threads randomly.
*/
public void init() {
int size = mkyNumber / oneTimeMkyNumber;
int lastSize = mkyNumber - oneTimeMkyNumber * size;
int id = 1;
for (int i = 0; i < size; i++) {
try {
Thread.sleep(timeInteval * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int j = 0; j < oneTimeMkyNumber; j++) {
double a = Math.random();
while (a == 0) {
a = Math.random();
}
double b = Math.random();
double temp = maxVelocity * a;
int velocity = (int)temp;
if (velocity == 0) {
velocity++;
}
Direction direction = null;
if (b > 0.5) {
direction = Direction.right;
} else {
direction = Direction.left;
}
MonkeyThread newThread = new MonkeyThread(id, velocity, direction, this);
newThread.start();
if (direction == Direction.left) {
synchronized (toLeftMonkeyList) {
toLeftMonkeyList.add(newThread.getMonkey());
allMonkeyThreads.add(newThread);
}
} else {
synchronized (toRightMonkeyList) {
toRightMonkeyList.add(newThread.getMonkey());
allMonkeyThreads.add(newThread);
}
}
id++;
}
}
for (int i = 0; i < lastSize; i++) {
double a = Math.random();
double b = Math.random();
double temp = maxVelocity * a;
int velocity = (int)temp;
Direction direction = null;
if (b > 0.5) {
direction = Direction.right;
} else {
direction = Direction.left;
}
MonkeyThread newThread = new MonkeyThread(id, velocity, direction, this);
newThread.start();
id++;
if (direction == Direction.left) {
synchronized (toLeftMonkeyList) {
toLeftMonkeyList.add(newThread.getMonkey());
allMonkeyThreads.add(newThread);
}
} else {
synchronized (toRightMonkeyList) {
toRightMonkeyList.add(newThread.getMonkey());
allMonkeyThreads.add(newThread);
}
}
}
hasStarted = true;
}
/**
* create thread based on the file.
*/
public void init1() {
for (int i = 0; i < line.size(); i++) {
String[] temp = line.get(i).split("=");
if (temp[0].equals("monkey")) {
String[] info = temp[1].split(",");
int monkeyTime = Integer.valueOf(info[0]);
int id = Integer.valueOf(info[1]);
Direction direction;
int velocity = Integer.valueOf(info[3]);
MonkeyThread newThread;
if (info[2].equals("R-L")) {
direction = Direction.left;
newThread = new MonkeyThread(id, velocity, direction, this);
} else {
direction = Direction.right;
newThread = new MonkeyThread(id, velocity, direction, this);
}
monkeyInfo.put(newThread, monkeyTime);
}
}
for (Map.Entry temp : monkeyInfo.entrySet()) {
if (temp.getValue() > timeDuration) {
timeDuration = temp.getValue();
}
}
int time = 0;
while (time <= timeDuration) {
for (Map.Entry temp : monkeyInfo.entrySet()) {
if (temp.getValue() == time) {
temp.getKey().start();
MonkeyThread newThread = temp.getKey();
if (newThread.getMonkey().getDirection() == Direction.left) {
synchronized (toLeftMonkeyList) {
toLeftMonkeyList.add(newThread.getMonkey());
allMonkeyThreads.add(newThread);
}
} else {
synchronized (toRightMonkeyList) {
toRightMonkeyList.add(newThread.getMonkey());
allMonkeyThreads.add(newThread);
}
}
}
}
time++;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
hasStarted = true;
}
public ArrayList getLadders() {
return ladders;
}
public void setLadders(ArrayList ladders) {
this.ladders = ladders;
}
public boolean hasStarted() {
return hasStarted;
}
public ArrayList getAllMonkeys() {
return allMonkeyThreads;
}
}
MonkeyThread类
本文的重头戏代码最多的一部分,也是完成大部分功能的一部分,但是一个一个看。
首先猴子线程的生命周期
选择一条合适的梯子来过河,其实根据实验的要求v1是需要两种及以上的选择策略的,但是这里直接展示了v2的情况只使用一种策略:默认猴子从右向左和从左向右的数目相差不多,那么就把梯子的一半向左,一半向右,但是由于整型的限制那么当梯子数是奇数的时候,向右的梯子数会比向左的梯子数少一个,那么在大多数情况下,向左的猴子的优先全部过河,那么在这时,向右的猴子获得全部的梯子权限,尽快过河。那么猴子怎么进行选择呢?就是猴子进行观察,当自己是对应那一侧的猴子中最快的猴子的时候,才进行选择。最后,在这些比较,选择的操作中都需要对左右岸的对应数组进行操作,那么就很可能发生线程冲突,因此,需要加一个同步锁,来保证程序的正确执行。
主要逻辑就是,在一个时间周期内,当前位置加上速度就是目标位置,检查如果当前位置的下一个到目标位置有被占用的横杆,那么就跳到被占用的横杆的前一个。如果目标位置超过了最后一个位置那么就直接跳转到最后一个横杆
跳转至最后一个横杆就表示move方法执行完毕,但还需要一秒的时间离开梯子,并且在离开梯子的同时需要检查梯子是否即将为空,为空就把方向置为中间
package lab;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
public class MonkeyThread extends Thread {
private final Monkey monkey;
private final MonkeyGenerator monkeyGenerator;
private int ladderNumber;
private int ladderLength;
private int position;
private long start;
private long end;
private final int interval = 1000;
/**
* The thread of the monkey.
* AF: the manager of a monkey.
* ladderNumber: the number of ladder that the monkey is on.
* ladderLength: the number of the rungs in a ladder.
* position: record the position of the monkey
* (done)
*/
public MonkeyThread(int id, int velocity,
Direction direction, MonkeyGenerator monkeyGenerator) {
monkey = new Monkey(id, velocity, direction);
this.monkeyGenerator = monkeyGenerator;
ladderLength = monkeyGenerator.getLadders().get(0).getRungs().size();
}
public Monkey getMonkey() {
return monkey;
}
/**
* choose the ladder, and move, and leave.
*/
@Override
public void run() {
start = System.currentTimeMillis();
Logger logger = LogManager.getLogger("第" + String.valueOf(monkey.getID()) + "个猴子 ");
end = 0;
while (true) {
end = System.currentTimeMillis();
String direction = "正在左岸等待";
if (monkey.getDirection() == Direction.left) {
direction = "正在右岸等待";
}
logger.info(direction + "离出生已经" + (end - start) / 1000 + "秒");
boolean isfastest = true;
if (monkey.getDirection() == Direction.right) {
synchronized (monkeyGenerator.getToRightMonkeyList()) {
for (int i = 0; i < monkeyGenerator.getToRightMonkeyList().size(); i++) {
if (monkeyGenerator.getToRightMonkeyList().get(i).getVelocity()
> monkey.getVelocity()) {
isfastest = false;
break;
}
}
}
}
if (monkey.getDirection() == Direction.left) {
synchronized (monkeyGenerator.getToLeftMonkeyList()) {
for (int i = 0; i < monkeyGenerator.getToLeftMonkeyList().size(); i++) {
if (monkeyGenerator.getToLeftMonkeyList().get(i).getVelocity()
> monkey.getVelocity()) {
isfastest = false;
break;
}
}
}
}
try {
Thread.sleep(interval);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (isfastest) {
break;
}
}
while (!choose2()) {
try {
Thread.sleep(interval);
} catch (InterruptedException e) {
e.printStackTrace();
}
end = System.currentTimeMillis();
String direction = "正在左岸等待";
if (monkey.getDirection() == Direction.left) {
direction = "正在右岸等待";
}
logger.info(direction + "离出生已经" + (end - start) / 1000 + "秒");
}
if (monkey.getDirection() == Direction.right) {
moveRight();
try {
Thread.sleep(interval);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (position == ladderLength - 1) {
synchronized (monkeyGenerator.getLadders()
.get(ladderNumber).getRungs().get(ladderLength - 1)) {
monkeyGenerator.getLadders().get(ladderNumber)
.getRungs().get(ladderLength - 1).setMonkey(null);
monkeyGenerator.getLadders().get(ladderNumber).getRungs().get(ladderLength - 1).unTake();
}
boolean isempty = true;
for (int i = 0; i < ladderLength; i++) {
if (monkeyGenerator.getLadders().get(ladderNumber).getRungs().get(i).isTaken()) {
isempty = false;
break;
}
}
if (isempty) {
monkeyGenerator.getLadders().get(ladderNumber).setDirection(Direction.neutral);
}
}
} else {
moveLeft();
try {
Thread.sleep(interval);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (position == 0) {
synchronized (monkeyGenerator.getLadders().get(ladderNumber).getRungs().get(0)) {
monkeyGenerator.getLadders().get(ladderNumber).getRungs().get(0).setMonkey(null);
monkeyGenerator.getLadders().get(ladderNumber).getRungs().get(0).unTake();
}
boolean isempty = true;
for (int i = 0; i < ladderLength; i++) {
if (monkeyGenerator.getLadders().get(ladderNumber).getRungs().get(i).isTaken()) {
isempty = false;
break;
}
}
if (isempty) {
monkeyGenerator.getLadders().get(ladderNumber).setDirection(Direction.neutral);
}
}
}
end = System.currentTimeMillis();
String direction = "抵达右岸";
if (monkey.getDirection() == Direction.left) {
direction = "抵达左岸";
}
logger.info(direction + "总耗时" + (end - start) / 1000 + "秒");
}
public long getStart() {
return start;
}
public long getEnd() {
return end;
}
/**
* choosing strategy 1
* choose a ladder that is not taken by any other monkeys.
* @return
*/
private boolean choose1() {
int maxTake = monkeyGenerator.getLadders().size() / 2;
synchronized (monkeyGenerator.getLadders()) {
boolean selectAll = false;
if (monkeyGenerator.getToLeftMonkeyList().isEmpty()) {
selectAll = true;
for (int i = maxTake; i < monkeyGenerator.getLadders().size(); i++) {
if (monkeyGenerator.getLadders().get(i).getDirection() != Direction.neutral) {
selectAll = false;
}
}
}
if (monkey.getDirection() == Direction.right) {
if (selectAll) {
maxTake = monkeyGenerator.getLadders().size();
System.err.println(maxTake);
}
for (int i = 0; i < maxTake; i++) {
if (monkeyGenerator.getLadders().get(i).getDirection() == Direction.neutral
&& !monkeyGenerator.getLadders().get(i).getRungs().get(0).isTaken()) {
monkeyGenerator.getLadders().get(i).setDirection(Direction.right);
monkeyGenerator.getLadders().get(i).getRungs().get(0).setTaken();
monkeyGenerator.getLadders().get(i).getRungs().get(0).setMonkey(monkey);
position = 0;
synchronized (monkeyGenerator.getToRightMonkeyList()) {
monkeyGenerator.getToRightMonkeyList().remove(monkey);
}
ladderNumber = i;
return true;
}
}
}
}
if (monkey.getDirection() == Direction.left) {
synchronized (monkeyGenerator.getLadders()) {
for (int i = maxTake; i < monkeyGenerator.getLadders().size(); i++) {
if (monkeyGenerator.getLadders().get(i).getDirection() == Direction.neutral
&& !monkeyGenerator.getLadders().get(i).getRungs().get(ladderLength - 1).isTaken()) {
monkeyGenerator.getLadders().get(i).setDirection(Direction.left);
monkeyGenerator.getLadders().get(i).getRungs().get(ladderLength - 1).setTaken();
monkeyGenerator.getLadders().get(i).getRungs().get(ladderLength - 1).setMonkey(monkey);
position = ladderLength - 1;
synchronized (monkeyGenerator.getToLeftMonkeyList()) {
monkeyGenerator.getToLeftMonkeyList().remove(monkey);
}
ladderNumber = i;
return true;
}
}
}
}
return false;
}
/**
* choosing strategy 2
* choose only when the monkey itself is the fastest monkey among the left.
* @return
*/
private boolean choose2() {
int maxTake = monkeyGenerator.getLadders().size() / 2;
boolean selectAll = false;
if (monkeyGenerator.getToLeftMonkeyList().isEmpty()) {
selectAll = true;
for (int i = maxTake; i < monkeyGenerator.getLadders().size(); i++) {
if (monkeyGenerator.getLadders().get(i).getDirection() != Direction.neutral) {
selectAll = false;
}
if (monkeyGenerator.getLadders().get(i).getDirection() == Direction.right) {
selectAll = true;
}
}
}
if (monkey.getDirection() == Direction.right) {
if (maxTake == 0) {
maxTake++;
}
if (selectAll) {
maxTake = monkeyGenerator.getLadders().size();
}
synchronized (monkeyGenerator.getLadders()) {
for (int i = 0; i < maxTake; i++) {
if ((monkeyGenerator.getLadders().get(i).getDirection() == Direction.neutral
|| monkeyGenerator.getLadders().get(i).getDirection() == Direction.right)
&& !monkeyGenerator.getLadders().get(i).getRungs().get(0).isTaken()) {
monkeyGenerator.getLadders().get(i).setDirection(Direction.right);
monkeyGenerator.getLadders().get(i).getRungs().get(0).setTaken();
monkeyGenerator.getLadders().get(i).getRungs().get(0).setMonkey(monkey);
position = 0;
synchronized (monkeyGenerator.getToRightMonkeyList()) {
monkeyGenerator.getToRightMonkeyList().remove(monkey);
}
ladderNumber = i;
return true;
}
}
}
}
if (monkey.getDirection() == Direction.left) {
synchronized (monkeyGenerator.getLadders()) {
for (int i = maxTake; i < monkeyGenerator.getLadders().size(); i++) {
if ((monkeyGenerator.getLadders().get(i).getDirection() == Direction.neutral
|| monkeyGenerator.getLadders().get(i).getDirection() == Direction.left)
&& !monkeyGenerator.getLadders().get(i).getRungs().get(ladderLength - 1).isTaken()) {
monkeyGenerator.getLadders().get(i).setDirection(Direction.left);
monkeyGenerator.getLadders().get(i).getRungs().get(ladderLength - 1).setTaken();
monkeyGenerator.getLadders().get(i).getRungs().get(ladderLength - 1).setMonkey(monkey);
position = ladderLength - 1;
synchronized (monkeyGenerator.getToLeftMonkeyList()) {
monkeyGenerator.getToLeftMonkeyList().remove(monkey);
}
ladderNumber = i;
return true;
}
}
}
}
return false;
}
/**
* move towards right, if one of the rungs between the current position and
* target position is taken, move to the rung in front of that.
*/
private void moveRight() {
Logger logger = LogManager.getLogger("第" + String.valueOf(monkey.getID()) + "个猴子 ");
while (position + 1 < ladderLength) {
try {
Thread.sleep(interval);
} catch (InterruptedException e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
logger.info("正在第" + (ladderNumber + 1) + "个梯子第" + (position + 1)
+ "个踏板,从左向右" + "离出生已经" + (end - start) / 1000 + "秒");
int targetPosition = position + monkey.getVelocity();
if (targetPosition + 1 > ladderLength) {
targetPosition = ladderLength - 1;
}
int realPosition = targetPosition;
for (int i = position + 1; i <= targetPosition; i++) {
if (monkeyGenerator.getLadders().get(ladderNumber).getRungs().get(i).isTaken()) {
realPosition = i - 1;
}
}
if (realPosition > position) {
synchronized (monkeyGenerator.getLadders().get(ladderNumber).getRungs().get(position)) {
monkeyGenerator.getLadders().get(ladderNumber).getRungs().get(realPosition).setTaken();
monkeyGenerator.getLadders()
.get(ladderNumber).getRungs().get(realPosition).setMonkey(monkey);
monkeyGenerator.getLadders().get(ladderNumber).getRungs().get(position).setMonkey(null);
monkeyGenerator.getLadders().get(ladderNumber).getRungs().get(position).unTake();
position = realPosition;
}
}
}
}
/**
* move towards left, if one of the rungs between the current position and
* target position is taken, move to the rung in front of that.
*/
private void moveLeft() {
Logger logger = LogManager.getLogger("第" + String.valueOf(monkey.getID()) + "个猴子 ");
while (position > 0) {
try {
Thread.sleep(interval);
} catch (InterruptedException e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
logger.info("正在第" + (ladderNumber + 1) + "个梯子第" + (position + 1)
+ "个踏板,从右向左" + "离出生已经" + (end - start) / 1000 + "秒");
int targetPosition = position - monkey.getVelocity();
if (targetPosition <= 0) {
targetPosition = 0;
}
int realPosition = targetPosition;
for (int i = position - 1; i >= targetPosition; i--) {
if (monkeyGenerator.getLadders().get(ladderNumber).getRungs().get(i).isTaken()) {
realPosition = i + 1;
}
}
if (realPosition < position) {
synchronized (monkeyGenerator.getLadders().get(ladderNumber).getRungs().get(position)) {
monkeyGenerator.getLadders().get(ladderNumber).getRungs().get(realPosition).setTaken();
monkeyGenerator.getLadders()
.get(ladderNumber).getRungs().get(realPosition).setMonkey(monkey);
monkeyGenerator.getLadders().get(ladderNumber).getRungs().get(position).setMonkey(null);
monkeyGenerator.getLadders().get(ladderNumber).getRungs().get(position).unTake();
position = realPosition;
}
}
}
}
}
mainThread类
主线程也就是猴子生成器执行的地方,猴子生成器的初始变量都在此获得,同时梯子的对象实在mainThread类中分配,也是创建ladders和启动猴子生成器的地方
package lab;
import java.util.ArrayList;
public class MainThread extends Thread {
private final int ladderNumber;
private final int ladderLength;
private final int timeInteval;
private final int oneTimeMkyNumber;
private final int mkyNumber;
private final int maxVelocity;
private final MonkeyGenerator monkeyGenerator;
private final ArrayList ladders = new ArrayList<>();
/**Class mainThread, used to create ladders and start the monkeyGenerator.
1. 8 variables included.
2. Mutable class.
3. REP: null
4. AF:
5. ladderNumber: the number of the ladders.
6. ladderLength: the number of the rungs in a single ladder.
7. timeInteval: control the speed of creating monkeys.
8. oneTimeMkyNumber: the number of the new monkeys created one time.
9. mkyNumber: the number of all the monkeys.
10. maxVelocity: the max speed of the monkeys.
11. monkeyGenerator: used to generator monkeys.
12. RI: the first 6 variables are final, can't be changed.
13. (done)
*/
public MainThread(
int ladderNumber, int ladderLength, int timeInteval,
int oneTimeMkyNumber, int mkyNumber,int maxVelocity,
MonkeyGenerator monkeyGenerator) {
this.ladderNumber = ladderNumber;
this.ladderLength = ladderLength;
this.timeInteval = timeInteval;
this.oneTimeMkyNumber = oneTimeMkyNumber;
this.mkyNumber = mkyNumber;
this.maxVelocity = maxVelocity;
this.monkeyGenerator = monkeyGenerator;
init();
}
private synchronized void init() {
for (int i = 0; i < ladderNumber; i++) {
Ladder newLadder = new Ladder(i, ladderLength);
ladders.add(newLadder);
}
}
@Override
public void run() {
monkeyGenerator.init();
}
public MonkeyGenerator getMonkeyGenerator() {
return monkeyGenerator;
}
public int getLadderNumber() {
return ladderNumber;
}
public int getLadderLength() {
return ladderLength;
}
public int getTimeInteval() {
return timeInteval;
}
public int getOneTimeMkyNumber() {
return oneTimeMkyNumber;
}
public int getMkyNumber() {
return mkyNumber;
}
public int getMaxVelocity() {
return maxVelocity;
}
public ArrayList getLadders() {
return ladders;
}
}
Builder类
主程序执行的类,run方法中是主要的代码逻辑
除此以外builder还有一个责任就是向控制台输出一个文本信息,以及在窗体中的信息也是有builder产生
package lab;
public class Builder extends Thread {
private final int ladderNumber;
private final int ladderLength;
private final int timeInteval;
private final int oneTimeMkyNumber;
private final int mkyNumber;
private final int maxVelocity;
private long time;
private long start;
private long end;
private boolean isStopped = false;
private double index;
private String infoString = "";
/**Class builder, used to build a monkeyThread and a mainThread.
* 12 variables included.
* Mutable class.
* REP: null
* AF:
* ladderNumber: the number of the ladders.
* ladderLength: the number of the rungs in a single ladder.
* 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.
* time: used to record the time taken.
* start: used to compute the time.
* end: used to compute the time.
* isStopped: mark the end of this thread.
* index: give the index to the Draw.
* infoString: print the info to the GUI.
* RI: the first 6 variables are final, can't be changed.
* (done)
*/
public double getIndex() {
return index;
}
public String getInfoString() {
return infoString;
}
public boolean isStopped() {
return isStopped;
}
public long getTime() {
return time;
}
/**
* build the whole system.
* @param ladderNumber
*
* @param ladderLength
*
* @param timeInteval
*
* @param oneTimeMkyNumber
*
* @param mkyNumber
*
* @param maxVelocity
*
*/
public Builder(int ladderNumber, int ladderLength, int timeInteval,
int oneTimeMkyNumber, int mkyNumber, int maxVelocity) {
this.ladderNumber = ladderNumber;
this.ladderLength = ladderLength;
this.timeInteval = timeInteval;
this.oneTimeMkyNumber = oneTimeMkyNumber;
this.mkyNumber = mkyNumber;
this.maxVelocity = maxVelocity;
}
/**
* First of all, create a new MonkeyGenerator, then the mainThread used to create the
* ladder and start the monkeyGenerator.
* Then the generator gets the ladders, mainThread start the generator.
*/
@Override
public void run() {
start = System.currentTimeMillis();
MonkeyGenerator monkeyGenerator =
new MonkeyGenerator(timeInteval, oneTimeMkyNumber, mkyNumber, maxVelocity);
MainThread mainThread =
new MainThread(ladderNumber, ladderLength, timeInteval, oneTimeMkyNumber, mkyNumber,
maxVelocity, monkeyGenerator);
monkeyGenerator.setLadders(mainThread.getLadders());
mainThread.start();
printGraph(mainThread, monkeyGenerator);
isStopped = true;
}
/**
* used to print the info to the console, and give the update the info String.
* @param mainThread
*
* @param monkeyGenerator
*
*/
private void printGraph(MainThread mainThread, MonkeyGenerator monkeyGenerator) {
boolean exit = false;
while (!exit) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
StringBuilder temp = new StringBuilder();
System.out.println(mainThread.getMonkeyGenerator().getToLeftMonkeyList().size() + ":"
+ mainThread.getMonkeyGenerator().getToRightMonkeyList().size());
temp.append(mainThread.getMonkeyGenerator().getToLeftMonkeyList().size() + ":"
+ mainThread.getMonkeyGenerator().getToRightMonkeyList().size() + "\n");
for (int i = 0; i < ladderNumber; i++) {
System.out.print(i + 1);
temp.append(String.valueOf(i + 1));
for (int j = 0; j < ladderLength; j++) {
System.out.print("|");
temp.append("|");
synchronized (mainThread.getLadders().get(i).getRungs().get(j)) {
if (mainThread.getLadders().get(i).getRungs().get(j).isTaken()) {
System.out.printf("%3d",
mainThread.getLadders().get(i).getRungs().get(j).getMonkey().getID());
temp.append(String.format("%03d",
mainThread.getLadders().get(i).getRungs().get(j).getMonkey().getID()));
} else {
System.out.print(" ");
temp.append("000");
}
}
}
System.out.print("|");
System.out.println();
temp.append("|\n");
}
System.out.println();
temp.append("\n\n\n");
boolean isTmpty = true;
if (mainThread.getMonkeyGenerator().getToLeftMonkeyList().size() != 0) {
isTmpty = false;
}
if (mainThread.getMonkeyGenerator().getToRightMonkeyList().size() != 0) {
isTmpty = false;
}
for (int i = 0; i < mainThread.getLadders().size(); i++) {
if (mainThread.getLadders().get(i).getDirection() != Direction.neutral) {
isTmpty = false;
}
}
if (isTmpty && monkeyGenerator.hasStarted()) {
exit = true;
}
end = System.currentTimeMillis();
time = (end - start) / 1000;
temp.append("用时: " + String.valueOf((end - start) / 1000) + "秒");
infoString = temp.toString();
}
for (int i = 0; i < monkeyGenerator.getAllMonkeys().size(); i++) {
System.out.println((monkeyGenerator.getAllMonkeys().get(i).getStart()
- monkeyGenerator.getStart()) / 1000 + ":"
+ (monkeyGenerator.getAllMonkeys().get(i).getEnd()
- monkeyGenerator.getStart()) / 1000);
}
index = 0;
for (int i = 0; i < monkeyGenerator.getAllMonkeys().size(); i++) {
for (int j = i + 1; j < monkeyGenerator.getAllMonkeys().size(); j++) {
double startDiff = monkeyGenerator.getAllMonkeys().get(i).getStart()
- monkeyGenerator.getAllMonkeys().get(j).getStart();
double endDiff = monkeyGenerator.getAllMonkeys().get(i).getEnd()
- monkeyGenerator.getAllMonkeys().get(j).getEnd();
if (startDiff * endDiff > 0) {
index++;
} else {
index--;
}
}
}
int n = monkeyGenerator.getAllMonkeys().size();
index = index / (n * (n - 1) / 2);
}
public int getMkyNumber() {
return mkyNumber;
}
}
以上是一些比较重要的ADT和设计模式,剩余的都是一些完善的代码
完整版代码:https://github.com/1173710118/CSDN-lab6
v3的过程与v1 v2大同小异,区别是猴子生成器的数据并不是随机而是根据文件输入决定
n=5
h=20
monkey=<0,1,R->L,2>
monkey=<0,2,R->L,5>
monkey=<0,3,R->L,5>
monkey=<0,4,R->L,2>
monkey=<0,5,R->L,5>
这个文本代表一共有五个梯子,梯子有20个横杆,monkey的信息分别是
v3特殊的主线程类
package lab;
import java.util.ArrayList;
public class MainThreadRainy extends Thread {
private final int ladderNumber;
private final int ladderLength;
private final MonkeyGenerator monkeyGenerator;
private final ArrayList ladders = new ArrayList<>();
/**
* Another version of the mainThread,
* specially designed for V3.
* (done)
*/
public MainThreadRainy(int ladderNumber, int ladderLength,
MonkeyGenerator monkeyGenerator) {
this.ladderNumber = ladderNumber;
this.ladderLength = ladderLength;
this.monkeyGenerator = monkeyGenerator;
init();
}
/**
* build ladders.
*/
public synchronized void init() {
for (int i = 0; i < ladderNumber; i++) {
Ladder newLadder = new Ladder(i, ladderLength);
ladders.add(newLadder);
}
}
@Override
public void run() {
monkeyGenerator.init1();
}
public int getLadderNumber() {
return ladderNumber;
}
public int getLadderLength() {
return ladderLength;
}
public MonkeyGenerator getMonkeyGenerator() {
return monkeyGenerator;
}
public ArrayList getLadders() {
return ladders;
}
}
v3的特殊builder类
package lab;
import java.io.BufferedReader;
import java.io.FileReader;
import java.util.ArrayList;
public class BuilderRainy extends Thread {
private int ladderNumber;
private int ladderLength;
private long time;
private long start;
private long end;
private boolean isStopped = false;
private double index;
private String infoString = "";
/**
* All variables are the same as the Builder's.
* Specially designed for V3.
* (done)
*/
public String getInfoString() {
return infoString;
}
private final ArrayList line = new ArrayList<>();
public long getTime() {
return time;
}
public long getStart() {
return start;
}
public long getEnd() {
return end;
}
public boolean isStopped() {
return isStopped;
}
public double getIndex() {
return index;
}
public ArrayList getLine() {
return line;
}
/**
* Read the file of the pathname.
* @param pathname
*
*/
public BuilderRainy(String pathname) {
try {
FileReader reader = new FileReader(pathname);
BufferedReader br = new BufferedReader(reader);
String tempString;
while ((tempString = br.readLine()) != null) {
line.add(tempString);
}
br.close();
} catch (Exception e) {
e.printStackTrace();
}
for (int i = 0; i < line.size(); i++) {
line.set(i, line.get(i).replaceAll(" ", ""));
line.set(i, line.get(i).replaceAll("<", ""));
line.set(i, line.get(i).replaceAll(">", ""));
}
for (int i = 0; i < line.size(); i++) {
String[] infoStrings = line.get(i).split("=");
if (infoStrings[0].equals("n")) {
ladderNumber = Integer.valueOf(infoStrings[1]);
}
if (infoStrings[0].equals("h")) {
ladderLength = Integer.valueOf(infoStrings[1]);
}
}
}
@Override
public void run() {
start = System.currentTimeMillis();
MonkeyGenerator monkeyGenerator = new MonkeyGenerator(line);
MainThreadRainy mainThreadRainy =
new MainThreadRainy(ladderNumber, ladderLength, monkeyGenerator);
monkeyGenerator.setLadders(mainThreadRainy.getLadders());
mainThreadRainy.start();
printGraph(mainThreadRainy, monkeyGenerator);
isStopped = true;
}
private void printGraph(MainThreadRainy mainThreadRainy,
MonkeyGenerator monkeyGenerator) {
boolean exit = false;
while (!exit) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
StringBuilder temp = new StringBuilder();
System.out.println(mainThreadRainy.getMonkeyGenerator().getToLeftMonkeyList().size() + ":"
+ mainThreadRainy.getMonkeyGenerator().getToRightMonkeyList().size());
temp.append(mainThreadRainy.getMonkeyGenerator().getToLeftMonkeyList().size() + ":"
+ mainThreadRainy.getMonkeyGenerator().getToRightMonkeyList().size() + "\n");
for (int i = 0; i < ladderNumber; i++) {
System.out.print(i + 1);
temp.append(String.valueOf(i + 1));
for (int j = 0; j < ladderLength; j++) {
System.out.print("|");
temp.append("|");
synchronized (mainThreadRainy.getLadders().get(i).getRungs().get(j)) {
if (mainThreadRainy.getLadders().get(i).getRungs().get(j).isTaken()) {
System.out.printf("%3d",
mainThreadRainy.getLadders().get(i).getRungs().get(j).getMonkey().getID());
temp.append(String.format("%03d",
mainThreadRainy.getLadders().get(i).getRungs().get(j).getMonkey().getID()));
} else {
System.out.print(" ");
temp.append("000");
}
}
}
System.out.print("|");
System.out.println();
temp.append("|\n");
}
System.out.println();
temp.append("\n\n\n");
boolean isTmpty = true;
if (mainThreadRainy.getMonkeyGenerator().getToLeftMonkeyList().size() != 0) {
isTmpty = false;
}
if (mainThreadRainy.getMonkeyGenerator().getToRightMonkeyList().size() != 0) {
isTmpty = false;
}
for (int i = 0; i < mainThreadRainy.getLadders().size(); i++) {
if (mainThreadRainy.getLadders().get(i).getDirection() != Direction.neutral) {
isTmpty = false;
}
}
if (isTmpty && monkeyGenerator.hasStarted()) {
exit = true;
}
end = System.currentTimeMillis();
time = (end - start) / 1000;
temp.append("用时: " + String.valueOf((end - start) / 1000) + "秒");
infoString = temp.toString();
}
for (int i = 0; i < monkeyGenerator.getAllMonkeys().size(); i++) {
System.out.println((monkeyGenerator.getAllMonkeys().get(i).getStart()
- monkeyGenerator.getStart()) / 1000 + ":"
+ (monkeyGenerator.getAllMonkeys().get(i).getEnd()
- monkeyGenerator.getStart()) / 1000);
}
index = 0;
for (int i = 0; i < monkeyGenerator.getAllMonkeys().size(); i++) {
for (int j = i + 1; j < monkeyGenerator.getAllMonkeys().size(); j++) {
double startDiff = monkeyGenerator.getAllMonkeys().get(i).getStart()
- monkeyGenerator.getAllMonkeys().get(j).getStart();
double endDiff = monkeyGenerator.getAllMonkeys().get(i).getEnd()
- monkeyGenerator.getAllMonkeys().get(j).getEnd();
if (startDiff * endDiff > 0) {
index++;
} else {
index--;
}
}
}
int n = monkeyGenerator.getAllMonkeys().size();
index = index / (n * (n - 1) / 2);
}
}
其实这是我最想说的一部分就是分享自己的经验。多线程的程序的真的是很磨人的过程,因为每次执行结果都不同,而bug出现的位置有时候很难以发现。
那么首先分析一下第四个问题,因为我的代码的选择策略是快的猴子有优先选择权,那么也就是说出现问题的那一侧的猴子中的最快的一个停下来了,那么可能的原因就是同步没有完全,有的地方没有完全同步。发现是检测自己是否是最快的的代码缺少了同步。
那么产生的原因是什么呢?一开始我为了测试程序,选择策略选择的是naive的方法,就是只选择空的梯子,那么当策略改为新策略时,猴子需要在toLeftMonkeyList,toRightMonkeyList,中进行比较,而原来的策略中,这两个数组只有在上梯子和下梯子才会执行,那么由于rung是同步的,这两个数组实际上不需要同步,而新策略恰恰会产生问题,于是出现了错误。
那么很有意思的一点是,当我改正了同步时,问题2 3也同时消失了,那么其实仔细想一想也是因为没有同步导致的问题。这就是多线程恐怖的地方了,一个bug可能有三种的形式,这样琢磨不定让人很难去分析问题的原因。
这是软件构造课程的最后一个实验,真的是如释重负,想起那么多日日夜夜,感觉自己真的还是有一些收获。不过客观的说,我觉得实验6是六个实验中最合理的一个,因为它是一种特殊的实现形式,而不是一种设计模式,实验六的实验要求更加简洁,而且对于代码的具体限制放宽了很多,确实在这种自由度比较高的模式下,才能更好地体会需要的工具。而前面的实验很多设计模式,听着看着感觉很好,但是具体实验的要求很难真正体会到设计模式的优越性。